@wade123
2019-03-26T07:40:45.000000Z
字数 5243
阅读 1618
Python入门爬虫与数据分析
摘要: 以澎湃「美数课」信息图栏目文章为例,介绍爬取 Ajax 多页图片。
前几篇爬虫文章的网站有一个特点,即翻页时 URL 参数会跟着变化,这种网页在爬取多页时递归参数容易构造,爬取一页就能爬多页,没有太大难度。
还有一类常见的网页在翻页时,URL 不会显示页码变化,网页中也没有「下一页」按钮,取而代之的是「加载更多」或者下拉会自动刷新内容。这类网页通常采用的是 Ajax 技术,提取网页内容和以前的方法不同,需另辟蹊径。下面以澎湃「美数课」栏目信息图为例,爬取该栏目从 2015 年至今全部 437 篇文章图片。
URL:https://www.thepaper.cn/list_25635

本文知识点:
尝试在上面的网页中不断下拉,会加载出更多新的文章,网址 URL 也保持不变,这种形式的网页很常见,它们普遍采用了Ajax 技术。
Ajax 全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML),
它不是一门编程语言,而是利用 JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。
传统网页如果想更新内容,需要刷新整个页面,使用 Ajax 更新内容只需刷新网页部分内容。这个过程的背后实际上是后台与服务器进行了数据交互,获取到数据之后利用 JavaScript 改变网页,实现网页内容的更新 。
了解 Ajax 技术后,下面就来实战爬取该网页的所有文章图片。
主页右键点击检查,按f5刷新会弹出很多链接文件,回到第一个文件:list_25635,ctrl+f搜索第一篇文章的标题:"娃娃机生意经"是否在这个文件中,可以看到顺利找到对应名称,意味着文章的源代码很可能是在这个文件里。

下拉鼠标更新新的文章,第一篇文章标题含有「金砖峰会」,再重复搜索一次看是否还能够在文件中找到源代码,经尝试搜不到对应内容,意味着更新后的文章源代码不在这个文件中了。所以在这个文件中只能爬取前面的信息,后续 Ajax 刷新出来的内容爬取不到。

Network选项卡下勾选 ·XHR,这样只查看使用了 Ajax 的网页,可以看到只有 4 个结果,经尝试发现第 3 个链接中的Response中存放着网页文章信息,比如第一篇文章标题:娃娃机生意经|有没有好奇过抓娃娃机怎么又重新火起来了?。切换到 headers 选项卡,复制Request URL后面的链接到浏览器中,可以看到显示一部分文章的标题和图片内容。数一下的话,可以发现一共有 20 个文章标题,也就是对应着网页最初的 20 篇文章。

接下来,尝试下拉鼠标,同时关注Name列内容,会发现不断弹出新的链接,这些链接很可能隐藏着 Ajax 加载的文章信息。选中第一个load_index开头的链接,查看Response中的源代码,尝试搜索十年金砖峰这篇文章内容,果然在源代码中能够找到这篇文章。

再次复制Request URL后面的链接到浏览器,可以看到包含了更新的 20 文章信息。

到这儿就能确定了,每个链接存放着网页中的 20 文章信息,只要爬取每个 URL 链接中的图片,就能够下载该栏目下所有文章的图片。
下面,我们来分析一下这些 URL,看看能不能找到规律,如果能够找到规律,就可以简单的够构造递增参数,用 for 循环批量下载每个 URL 中的图片,前 3 个链接如下:
https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=2&isList=true&lastTime=1533169319712https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=3&isList=true&lastTime=1528625875167https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=4&isList=true&lastTime=1525499007926
发现pageidx参数值很有规律,以自然数递增,而 lastTime 参数值没有规律,需要测试一下这个参数有没有影响,复制第一个 URL,删掉&lastTime=1533169319712这一串字符,会发现网页一样能够正常打开,就说明着一对参数不影响网页内容,可以删除掉。这样所有 url 的区别就只是 pageidx参数不同了,构造 for 循环就可以生成所有 URL:
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=2https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=3https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=4
同时,需要确定最后一页的数值是多少。很简单,尽可能改一个较大的数值,再打开链接看是否有内容即可。比如改为 **10 **,打开发现有内容显示,再改为 30 就没有内容,尝试几次后确定最后一页是 25 页。
接下来就可以爬取全部页的图片,代码编写思路是先爬取一篇文章中的图片(这个爬取过程,上一篇一篇爬取网易的文章已经介绍过),然后爬取一个 URL 中的 20 篇文章图片,最后循环 25 页爬取所有图片,完整代码如下:
import requestsfrom bs4 import BeautifulSoupimport reimport osfrom hashlib import md5from requests.exceptions import RequestExceptionfrom multiprocessing import Poolfrom urllib.parse import urlencodeheaders = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'}# 1 获取索引界面网页内容def get_page_index(i):# 下载 1 页# url = 'https://www.thepaper.cn/newsDetail_forward_2370041'# 2 下载多页,构造 urlparas = {'nodeids': 25635,'pageidx': i}url = 'https://www.thepaper.cn/load_index.jsp?' + urlencode(paras)response = requests.get(url,headers = headers)if response.status_code == 200:return response.text# print(response.text) # 测试网页内容是否提取成功 ok# 2 解析索引界面网页内容def parse_page_index(html):soup = BeautifulSoup(html,'lxml')# 获取每页文章数num = soup.find_all(name = 'div',class_='news_li')for i in range(len(num)):yield{# 获取 title'title':soup.select('h2 a')[i].get_text(),# 获取图片 url,需加前缀'url':'https://www.thepaper.cn/' + soup.select('h2 a')[i].attrs['href']# print(url) # 测试图片链接}# 3 获取每条文章的详情页内容def get_page_detail(item):url = item.get('url')# 增加异常捕获语句try:response = requests.get(url,headers = headers)if response.status_code == 200:return response.text# print(response.text) # 测试网页内容是否提取成功except RequestException:print('网页请求失败')return None# 4 解析每条文章的详情页内容def parse_page_detail(html):soup = BeautifulSoup(html,'lxml')# 获取 titleif soup.h1: #有的网页没有 h1 节点,因此必须要增加判断,否则会报错title = soup.h1.string# 每个网页只能拥有一个<H1>标签,因此唯一items = soup.find_all(name='img',width =['100%','600'])# 有的图片节点用 width='100%'表示,有的用 600 表示,因此用 list 合并选择# https://blog.csdn.net/w_xuechun/article/details/76093950# print(items) # 测试返回的 img 节点 okfor i in range(len(items)):pic = items[i].attrs['src']# print(pic) #测试图片链接 okyield{'title':title,'pic':pic,'num':i # 图片添加编号顺序}# 5 下载图片def save_pic(pic):title = pic.get('title')# 标题规范命名:去掉符号非法字符| 等title = re.sub('[\/:*?"<>|]','-',title).strip()url = pic.get('pic')# 设置图片编号顺序num = pic.get('num')if not os.path.exists(title):os.mkdir(title)# 获取图片 url 网页信息response = requests.get(url,headers = headers)try:# 建立图片存放地址if response.status_code == 200:file_path = '{0}\{1}.{2}' .format(title,num,'jpg')# 文件名采用编号方便按顺序查看,而未采用哈希值 md5(response.content).hexdigest()if not os.path.exists(file_path):# 开始下载图片with open(file_path,'wb') as f:f.write(response.content)print('文章"{0}"的第{1}张图片下载完成' .format(title,num))else:print('该图片%s 已下载' %title)except RequestException as e:print(e,'图片获取失败')return Nonedef main(i):# get_page_index(i) # 测试索引界面网页内容是否获取成功 okhtml = get_page_index(i)data = parse_page_index(html) # 测试索引界面 url 是否获取成功 okfor item in data:# print(item) #测试返回的 dicthtml = get_page_detail(item)data = parse_page_detail(html)for pic in data:save_pic(pic)# 单进程if __name__ == '__main__':for i in range(1,26):main(i)# 多进程if __name__ == '__main__':pool = Pool()pool.map(main,[i for i in range(1,26)])pool.close()pool.join()
运行过程如下:

最后,所有图片都按照文件夹下载好了。
文章代码素材,可在下方链接中得到:
到这里,我们用了两篇文章练习如何批量爬取图片,希望你能够掌握。下一篇文章,我们使用 Selenium 自动爬虫。