本次目标:以今日头条为例来尝试通过分析Ajax请求来抓取网页数据的方法。(本文源于借鉴于崔神的网络爬虫实战案例)
1.抓取分析
在抓取之前,首先要分析主权去的逻辑,打开今日头条的首页http://www.toutiao.com/,右上角有搜索入口,这里尝试抓取街拍美图,输入'街拍'搜索,结果如图所示:
这时打开开发者工具,network,xhr选项,如图所示,可以发现一个Ajax请求,继续下拉页面皆可以发现后面的链接不断在温暖过增加,点开第一个 :
通过查看详情,可以发现页面中的数据(图片列表以及标题title)藏在里面:
再往下拉,可以看到标题的相关信息,而我们本次的目的就将图片列表的url以及标题给抓取下来,并且保存在本地,由于这些图片都是以组图的形式呈现出来的,所以保存的时候必须每一组分别保存下来,而我是打算保存的时候,每一组图都建立一个文件夹,文件夹的名称就是该组图的标题。
2.接下来,我们需要直接用Python模拟Ajax请求,将图片的url以及标题title提取出来,但在这之前,我们还需要分析一下URL的 规律,切换回headers选项卡,观察它的URL以及Headers信息,如图:
可以看到这是一个GET请求,请求URL的参数有offset,format,keyword,autoload,count和cur_tab,每一页之间的不同之处就爱在于这个offset,即这个offset就是偏移量,通过改变偏移量的值就可以得到多个Ajax请求信息,等会下面写程序会用到。
3.现在,可以正式说程序了,首先,实现方法get_page()来加载单个Ajax请求的结果,接下来,再实现一个解析方法,提取每条数据的image_list字段中的每一张图片链接,将图片链接和器对应的 标题一并返回,此时可以构成一个生成器,接下来,实现一个保存图片的方法save_image(),该方法中首先根据item中的title来创建文件夹,然后请求这个图片链接 ,获取图片的二进制数据,以二进制的形式写入文件。图片的名称可以使用其内容的MD5值,这样可以去除重复。最后,只需要构建一个offset数组,遍历offset,提取图片链接,并将其下载即可。这里还定义了分页的起始页和终止页,还利用了多线程的线程池,调用其map()方法实现多线程下载。
import requests
from urllib.parse import urlencode
import os
from hashlib import md5
from multiprocessing.pool import Pool
def get_one_page(offset): #实现方法来加载单个Ajax请求的结果,返回json的字符串格式
params={
'offset':offset,
'format':'json',
'keyword':'街拍',
'autoload':'true',
'count':'20',
'cur_tab':'1',
}
url='https://www.toutiao.com/search_content/?'+urlencode(params)
try:
response=requests.get(url)
if response.status_code==200:
return response.json()
except requests.ConnectionError as e:
return None
def get_images(json): #实现一个解析方法,提取每条数据的image_list字段中的每一张图片的链接,将图片链接和图片所属的标题一并返回,可以构造一个生成器
if json.get('data'):
for item in json.get('data'):
title=item.get('title')
images=item.get('image_list')
if images:
for image in images:
yield{
'image':'http:'+image.get('url'),
'title':title,
}
else:
return None
#定义一个保存图片的方法,首先根据item的title来创建文件夹,然后请求这个图片链接,获取图片的二进制数据,以二进制的形式写
#入文件。图片的名称可以使用其内容的MD5值,这样可以去除重复。
def save_image(item):
if not os.path.exists(item.get('title')):
os.mkdir(item.get('title'))
try:
response=requests.get(item.get('image'))
if response.status_code==200:
file_path='{0}/{1}{2}'.format(item.get('title'),md5(response.content).hexdigest(),'.jpg')
if not os.path.exists(file_path):
with open(file_path,'wb') as f:
f.write(response.content)
else:
print('Already Downloaded',file_path)
except requests.ConnectionError:
print('Failed to Save Image!')
except requests.exceptions.MissingSchema as rem:
print(rem)
def main(offset):
json=get_one_page(offset)
for item in get_images(json):
print(item)
save_image(item)
GROUP_START=1
GROUP_END=20
if __name__=='__main__':
pool=Pool()
groups=([x*20 for x in range(GROUP_START,GROUP_END+1)])
pool.map(main,groups) #利用多线程的线程池,调用其map方法实现多线程下载
pool.close()
pool.join()
4.好了,接下来我们可以看下运行结果,如图所示:
好了,今天的内容就到这儿了!