@wade123
2019-03-26T07:40:45.000000Z
字数 5243
阅读 1505
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=1533169319712
https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=3&isList=true&lastTime=1528625875167
https://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=2
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=3
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=4
同时,需要确定最后一页的数值是多少。很简单,尽可能改一个较大的数值,再打开链接看是否有内容即可。比如改为 **10 **,打开发现有内容显示,再改为 30 就没有内容,尝试几次后确定最后一页是 25 页。
接下来就可以爬取全部页的图片,代码编写思路是先爬取一篇文章中的图片(这个爬取过程,上一篇一篇爬取网易的文章已经介绍过),然后爬取一个 URL 中的 20 篇文章图片,最后循环 25 页爬取所有图片,完整代码如下:
import requests
from bs4 import BeautifulSoup
import re
import os
from hashlib import md5
from requests.exceptions import RequestException
from multiprocessing import Pool
from urllib.parse import urlencode
headers = {
'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 下载多页,构造 url
paras = {
'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')
# 获取 title
if 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 节点 ok
for i in range(len(items)):
pic = items[i].attrs['src']
# print(pic) #测试图片链接 ok
yield{
'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 None
def main(i):
# get_page_index(i) # 测试索引界面网页内容是否获取成功 ok
html = get_page_index(i)
data = parse_page_index(html) # 测试索引界面 url 是否获取成功 ok
for item in data:
# print(item) #测试返回的 dict
html = 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 自动爬虫。