运行环境: python : 3.5.2 requests:2.11.1 pymongo:3.5.1 MongoDB:3.4.7
项目目的:爬取今日头条中关于“街拍”图集中的图片,并保存相关数据到数据库
项目中需要用到的包
import re
from hashlib import md5
import pymongo
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
import requests
from config import *
from multiprocessing import Pool
下面来一个一个解释:
- json:本次爬取的数据多为 json 格式,所以需要將数据转换为 json 再进行下一步处理。
- md5:下载图片给图片命名时,有可能有些图片会重复,为了避免重复下载,使用 hashlib 模块的 md5 方法根据图片的内容给图片命名。(需要了解 hashlib 可点击这里)
- pymongo:python 连接 mongodb 的包
- RequestException:在进行网页请求时,可能会发生一些错误,在这里直接抛出
- requests:请求库
- config:代码中的一些配置信息
- Pool:多线程提高代码运行效率
网站分析
- 网站图集中不是采用翻页,而是随鼠标的下滑自动加载,其中只有请求参数 “offset” 改变(0、20、40递增)
- 每个图片集的 url 在 data 当中,如图
- 每张图片的 url 在网页文档的 gallery 中,可以采用正则获取 url
代码详情
一、请求索引页并解析
请求索引页
def get_page_index(offset, keyword):
'''返回请求索引页的代码详情'''
#请求参数设置
data = {
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': 20,
'cur_tab': 3
}
url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print("请求索引页出错")
return None
解析索引页
def parse_page_index(html):
'''解析索引页,获取页面url'''
# 將 html 转换为 json 格式的数据
data = json.loads(html)
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
二、请求详情页并解析
请求详情页
def get_page_detail(url):
'''获取详情页的代码'''
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('请求详情页出错', url)
return None
解析页面,获取图集名称和每张图片 url
def parse_page_detail(html, url):
'''解析详情页的代码,获取每张图片的 url '''
soup = BeautifulSoup(html, 'lxml')
title = soup.title.text
# 或者 title = soup.select('title')[0].get_text()
# print(title)
images_pattern = re.compile('gallery:(.*?)\ssiblingList:', re.S)
result = re.search(images_pattern, html)
if result:
# print(result.group(1)[:-5])
# 把结果转换为课处理的 json 格式
data = json.loads(result.group(1)[:-5])
if data and 'sub_images' in data.keys():
images = [item.get('url') for item in data.get('sub_images')]
for image in images:
download_imgae(image)
return {
'title': title,
'url': url,
'images': images
}
三、將数据保存到数据库
数据库配置信息
MONGO_URL = 'localhost' #数据库地址
MONGO_DB = 'toutiao' #数据库名称
MONGO_TABLE = 'toutiao' #表名称
连接数据库
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
將数据保存到数据库
def save_to_mongo(resutl):
'''把结果存储到 mongodb 数据库中'''
if db[MONGO_TABLE].insert(resutl):
print('存储到MongoDB成功', resutl)
return True
return False
下载图片并保存图片
下载图片
def download_imgae(url):
'''解析图片url'''
print('正在下载:', url)
try:
response = requests.get(url)
if response.status_code == 200:
save_image(response.content)
return None
except RequestException:
print('请求图片出错', url)
return None
保存图片到当前目录
def save_image(content):
'''保存文件'''
file_path = '{0}\{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
四、主函数
主函数
def main(offset):
'''主函数'''
html = get_page_index(offset, KEYWORD)
# print(html)
for url in parse_page_index(html):
html = get_page_detail(url)
if html:
result = parse_page_detail(html, url)
# print(result)
if result:
save_to_mongo(result)
程序入口
if __name__ == '__main__':
groups = [x * 20 for x in range(GROUP_START, GROUP_END)]
pool = Pool()
pool.map(main, groups)
参数说明
#需要爬取的页数配置参数
GROUP_START = 1
GROUP_END = 20
#爬取关键词
KEYWORD = '街拍'
巨坑之处
- 正则获取
gallery
内容时,gallery
是以,
结束,我当时匹配时无法用逗号作为匹配结束,只能再加上下一行的siblingList:
,但是这样的话就有空白符需要匹配,所以需要加上空白匹配符\s
。此时获得的数据最后为逗号,还不能直接转换为 json 格式的数据。这是本想着直接使用切片([:-1])即可去除逗号,然而事情并不是如此的简单,怎么都没想多逗号后面竟然还有四个空格(此处请容许我说句MMP)。现在在去分析,空白匹配符\s
没有匹配到换行符,难道siblingList:
前面还有空格!!! - 启用多线程时,连接数据库会发生一个错误,此时就需要在连接数据库时添加参数
connect=False
- 在解析页面时,有些页面不是我们需要的,无法解析到我们想要的结果。因此在执行下一步时就需要判断解析页面的结果。
结果展示
在短短的几分钟就下载了将近六百张图片,效率还是可以的
温馨提示:启动程序前记得启动数据库
完整代码和输出文件请访问:[https://github.com/xieys/python_spyder/tree/master/jiepai) 欢迎Follow和star