本次目标:以今日头条为例来尝试通过分析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.好了,接下来我们可以看下运行结果,如图所示:
好了,今天的内容就到这儿了!