项目地址:https://github.com/snjl/python.spider.jiepai.git
爬取网址
https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D
前期研究
访问网址后,会发现列表页有多种形式,第一种是视频,第二种是广告,第三种是所有图片在一个网页里面,第四种是图片需要跳转,我们做的是第四种。
通过f12开发者工具可以看到比较详细的信息。
点preserve log,可以刷新时保留记录,点XHR,可以看到传输过来的ajax数据。
列表页分析
列表是通过ajax传输过来的,参数有多个,在python爬取的时候需要传输:1
2
3
4
5
6
7
8
9
10
11data = {
'aid': 24,
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': 20,
'cur_tab': 3,
'from': 'gallery',
'pd': 'synthesis',
}
其中keyword是可以改变的,offset是偏移值,一般是为20的倍数,因为每次json传输20个数据。
列表页需要获取的是每个详情页的title和url。
详情页分析
分析详情页,可以发现每次跳转网页实际上不是ajax交互,而是在初始的网页中有数据,如下所示:1
2
3 gallery: JSON.parse("{\"count\":9,\"sub_images\":[{\"url\":\"http:\\/\\/p1.pstatp.com\\/origin\\/pgc-image\\/e4de890cc2084bc5b3557ee5b6ea0ed9\",\"width\":800,\"url_list\":[{\"url\":\"http:\\/\\/p1.pstatp.com\\/origin\\/pgc-image\\/e4de890cc2084bc5b3557ee5b6ea0ed9\"},
···
所以实际上已经把图的地址写在doc里,拿到网页的url,然后通过正则获取就可以得到地址。
代码功能
三部分
列表页
列表页需要两个函数,第一个是获取列表页的的response.text(需要进行错误处理),第二个是对列表页进行解析,获取到需要抓取的详情页的一些信息。
详情页
详情页里,需要通过正则获取到所有需要下载的链接,所以也是两个函数,一个获取详情页的response.text,一个获取详情页中的下载信息。
数据库、图片下载等
数据库使用mongodb,这里的代码可以插入在详情页的下载信息或者main函数中每次处理完一个详情页信息后进行存储;
图片下载同样处理,也可以放在获取到详情页的下载链接后的for循环遍历。
具体代码
列表页
获取列表页response.text1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def get_page_index(offset, keyword):
data = {
'aid': 24,
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': 20,
'cur_tab': 3,
'from': 'gallery',
'pd': 'synthesis',
}
url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)
print(url)
try:
response = requests.get(url=url, headers=headers)
if response.status_code == 200:
response.encoding = 'utf8' # 原编码不是utf8,会有一定乱码
return response.text
return None
except RequestException as e:
print(e)
print("请求json出错")
return None
传入keyword,offset获取列表页text。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def parse_page_index(html):
try:
data = json.loads(html)
# 判断data不为空,且'data'在keys中,并且data['data']不为空
if data and 'data' in data.keys() and data['data']:
items = data['data']
for item in items:
# 判断abstract为空,且app_info存在,且item['app_info']['db_name']为SITE
if item and 'abstract' in item.keys() and \
item['abstract'] == '' and 'app_info' in item.keys() \
and item['app_info']['db_name'] == 'SITE':
yield {
'title': item['title'],
'url': item['article_url']
}
except JSONDecodeError as e:
print(e)
由于拿到的是ajjax的json数据,所以可以通过json.loads()方法解析到数据,然后判断data是否为空,data里面是否有‘data’字段,如果有的话判断里面的每一个item是否有’abstract’字段和’app_info’字段,如果有,里面字段’db_name’的值为SITE(其实还是有误差)的才是第四类,通过这种方式获取的列表页信息才是比较干净的数据。最后返回title和url数据。
详情页
1 | def get_page_detail(url): |
获取详情页response.text代码。1
2
3
4
5
6
7
8
9
10def parse_page_detail(html):
images_pattern = re.compile('gallery: JSON.parse\("(.*?)"\)')
result = re.search(images_pattern, html)
if result:
result_data = re.sub(r'\\', '', result.group(1))
data = json.loads(result_data)
images = [sub_image['url'] for sub_image in data['sub_images']]
for image in images:
download_image(image)
return images
通过正则,获取内容后,由于反斜杠‘\’的存在影响了我们的进一步操作,因此,有必要把反斜杠去掉,需要注意的是,这里显示的每一个反斜杠,实际上源字符串中都有两个反斜杠,还有一个反斜杠是用来转义的,故print是并不显示。
因此,我们要实际上要去掉的是两个连续的反斜杠,使用re.sbu(r’\’,’’,results)进行替换
处理后,获得的就是一个images的url的list列表(此处还调用了一个下载图片的函数,后续会介绍),然后返回。
数据库和图片下载代码等
数据库可以使用配置类,所以另建一个配置文件config.py1
2
3
4
5
6
7
8MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'
GROUT_START = 1
GROUP_END = 40
KEYWORD = '街拍'
然后在使用mongo的时候调用1
2
3
4
5
6
7
8
9
10# 生成mongodb数据库对象
client = pymongo.MongoClient(MONGO_URL,connect=False)
db = client[MONGO_DB]
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print("存储到mongodb成功", result)
return True
return False
图片下载1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18def download_image(url):
print("正在下载", url)
try:
response = requests.get(url=url, headers=headers)
if response.status_code == 200:
save_image(response.content)
return None
except RequestException as e:
print(e)
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)
下载图片使用md5计算名称,存到当前路径。
main函数
1 | def main(offset): |
main函数参数为offset,keyword由配置文件传入。
main函数逻辑是,获取offset(偏移)后,获取这个json的数据并且解析出每一个item(dict类型,含有url和title),通过get_page_detail解析出每个网页的html,通过parse_page_detail解析出images,并且通过save_to_mongo存储到mongodb。
在调用parse_page_detail的时候就已经下载了图片。
运行函数
1 | if __name__ == '__main__': |
如果要多进程,使用:1
2
3
4if __name__ == '__main__':
groups = [x * 20 for x in range(GROUT_START, GROUP_END + 1)]
pool = Pool()
pool.map(main, groups)
groups为config.py中设置的1-40,groups实际上值为[20,40,60,…,400]。