js 爬虫抓取网页数据(今日头条为例来尝试通过分析Ajax请求来网页数据的方法 )
优采云 发布时间: 2022-04-04 16:02js 爬虫抓取网页数据(今日头条为例来尝试通过分析Ajax请求来网页数据的方法
)
文章目录
在本文中,我们以今日头条为例,尝试通过分析Ajax请求来抓取网页数据。本次要抓拍的目标是今日头条的街拍,抓拍保存。本节代码参考了python3网络爬虫实战的6.4节,但是由于网页上的一些东西已经更新,所以本文中的代码也做了相应的修改,使其可以正常采集数据。1.准备
首先,确保已安装 requests 库。
2.爬取分析
在爬取之前,先分析一下爬取的逻辑。打开今日头条首页,如图
在搜索框中输入街拍,结果如图
点击图片切换图片分类
然后打开开发者工具,查看所有网络请求。首先打开第一个网络请求,这个请求的URL是当前连接%E8%A1%97%E6%8B%8D&pd=atlas&source=search_subtab_switch&dvpf=pc&aid=4916&page_num=0,打开Preview标签查看Response Body。发现只有页面的一部分,如图。可以分析出下面的图片数据是通过Ajax加载,然后用JavaScript渲染出来的。
接下来,我们可以切换到 XHR 过滤选项卡,查看是否有任何 Ajax 请求。
果然有一个比较常规的ajax请求,看看它的结果是否收录页面中的相关数据。
点击rawData字段展开,可以发现有一个data字段,count字段为40,表示本次请求收录40张图片,data字段收录40条记录,分别是图片的url等信息。
这证实了数据确实是由 Ajax 加载的。
我们的目的是抓取里面的图片,这里一组图片对应上一个数据域中的一条数据。如图所示
因此,我们只需要提取并下载data中每条数据的img_url字段即可。创建一个文件夹来保存这些图片。
接下来,我们可以直接用Python来模拟这个Ajax请求,然后提取相关信息。但在此之前,我们还需要分析一下 URL 的规律。
切换回Headers选项卡,观察其请求URL和Headers信息,如图
可以看出这是一个GET请求。请求的参数有keyword、pd、source、dvpf、aid、page_num、search_json、rawJSON、search_id。我们需要找出这些参数的规则,因为这样可以方便地用程序构造请求。
接下来,滑动界面以加载更多结果。加载的时候可以发现NetWork中有很多Ajax请求,如图:
这里我们观察前后几个连接的请求变化,发现只有page_num参数在变化,每次变化都是1,所以可以找到规律,这个page_num就是偏移量,然后我们可以推断即count参数是一次得到的数据条数。所以我们可以使用page_num参数来控制分页。这样就可以通过接口批量获取数据,然后解析数据,下载图片。另外我们发现keyword参数使用的不是明文,而是加密的代码,可以借助python中的unquote包解决。
3.实战演练
我们刚刚分析了ajax请求的逻辑,下面使用程序来实现。
首先,实现方法 get_page() 来加载单个 Ajax 请求的结果。唯一的变化是参数page_num,所以它作为参数传递。另外需要注意的是,这里需要构造请求头,并且需要收录cooike,否则取不到Response。该信息可以在“标题”选项卡中找到:
构造参数可以在payload选项卡中找到
import requests
from urllib.parse import urlencode,quote,unquote
headers = {
'Host': 'so.toutiao.com',
'Referer': 'https://so.toutiao.com/search?keyword=%E8%A1%97%E6%8B%8D&pd=atlas&dvpf=pc&aid=4916&page_num=0&search_json={%22from_search_id%22:%222022040316335201021218304330C35A48%22,%22origin_keyword%22:%22%E8%A1%97%E6%8B%8D%22,%22image_keyword%22:%22%E8%A1%97%E6%8B%8D%22}',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest', # Ajax请求需要手动设置这里
'Cookie': 'passport_csrf_token=92a1b4e0108fb8384f5b81554b5b5424; tt_webid=7053044990251386398; _S_DPR=1.25; _S_IPAD=0; MONITOR_WEB_ID=7053044990251386398; ttwid=1%7CoqMwjUw5WGRdjYizT8quhnfpAchk3v_E3YLa1riJgrY%7C1648972603%7Cbaea5aebf3461426baaede977fa55b0efec3033a9f0d6a3e26684ba80f607ee9; _S_WIN_WH=1536_722'
}
def get_page(page_num):
params = {
'keyword':unquote('%E8%A1%97%E6%8B%8D') ,
'pd':'atlas',
'dvpf':'pc',
'aid': '4916' ,
'page_num':page_num,
'search_json':{"from_search_id":"2022040316335201021218304330C35A48","origin_keyword":"街拍","image_keyword":"街拍"},
'rawJSON':'1',
'search_id':'2022040317334901015013503043241DAC'
}
url = 'https://so.toutiao.com/search/?'+urlencode(params,headers)
try:
response = requests.get(url,headers=headers,params=params)
if response.status_code == 200:
return response.json()
except requests.ConnectionError:
return None
这里我们使用urlencode()方法构造请求的GET参数,然后使用requests请求链接。如果返回的状态码为 200,调用响应的 json() 方法将结果转换为 JSON 格式并返回。
接下来实现一个解析方法:提取每条数据的img_url字段中的图片链接,并返回图片链接。这时候就可以构建*敏*感*词*了。实现代码如下:
def get_images(json):
if json.get('rawData'):
images = json.get('rawData').get('data')
for image in images:
link = image.get('img_url')
yield {
'image':image.get('img_url'),
'title':"街拍",
'text':image.get('text')
}
接下来,实现一个用于保存图像的 save_image() 方法,其中 item 是前面的 get_images() 方法返回的字典。该方法首先根据item的标题创建一个文件夹,然后请求图片链接,获取图片的二进制数据,以二进制形式写入文件。图片名称可以使用其内容的MD5值,可以去除重复。代码显示如下:
import os
from hashlib import 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')
最后,只需构造一个数组,遍历,提取图片链接,下载即可:
from multiprocessing.pool import Pool
def main(page_num):
json = get_page(page_num)
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)]) #使用这种方式启动出现bug,原因还没有找到
# pool.map(main,groups)
# pool.close()
# pool.join()
for i in range(1,10):
main(i)
因为博主在使用线程池有bug,一直没找到原因,所以先用for循环
结果如下:
最后,给出本文的代码地址:
参考
[1].Python3网络爬虫开发实战.崔庆才.——6.4