前言

代码设计流程,先模拟ajax发送搜索“街拍美女”,提取返回json里面的article_url,再访问article_url,提取article_url响应的图片url,访问图片url并且保存图片。网上也有爬取今日头条图片的案例,但是很多都过时了,为了练习代码,本人亲自写了一个,网上其它案例没有那么复杂,我写的有点复杂化了,不过也比较详细。

获取索引页的链接

常规方法,对此页面发送请求,如对搜索发送请求,分析源代码,查看源代码发现在源代码里面是否有索引的链接,这里 的源代码没有找到链接,因此不能直接request请求此页面。


image

分析AJAX请求

通过滚动页面,发现请求不断增加,在XHR里面也多了一些链接,发现此页面是通过AJAX请求,模拟AJAX请求即可获取索引页的链接。


image


image

由于是json格式,无法直接用BeautifulSoup进行解析,先json.loads()转换为python对象,如下图,为字典对象:


4.png

article_url是在data键的值里面,可通过get("data")获取data键的值,在获取article_url的值,即get("article_url"),这里要对此结果进行分析,才能知道怎么提取里面的article_url,先看在浏览器显示,

如:'user_auth_info': {'auth_info': '广州富储资产管理有限公司融资经理 搞笑领域创作者', 'auth_type': '3', 'other_auth': {'interest': '搞笑领域创作者'}}

浏览器显示如下:


5.png

可以看到,冒号前面为key,后面为value,如果有多个value,则可折叠,这样通过浏览器查看更直观:


6.png

从上图看到,data下面有数字0、1,但数字不是字典的键,通过点击里面article_url链接,发现第一个索引url在5,第二个索引url在6,因此需要for遍历get("data")来获取artilce_url的值,article_url是data的值的键,不是data值嵌套里面的键,因此获取get("data")后,可直接get("article_url")。


7.png

贴上代码,模拟AJAX的请求代码,这里有个小坑,要添加headers的cookie,否则获取会不完整。

模拟AJAX的发送请求代码

def article1(offset):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428", #这里有个小坑,要添加headers的cookie,否则获取会不完整
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
"referer": "https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D%E7%BE%8E%E5%A5%B3",
"x-requested-with": "XMLHttpRequest",
}
params = {
'aid': 24,
'app_name': 'web_search',
'offset': offset,
'format': 'json',
'keyword': '街拍美女',
'autoload': 'true',
'count': 20,
'en_qc': 1,
'cur_tab': 1,
'from': 'search_tab',
'pd': 'synthesis',
'timestamp': 1557973762622,
}
url = 'https://www.toutiao.com/api/search/content/?'+ urlencode(params)
try:
r = requests.get(url,headers=headers)
if r.status_code == 200:
res=json.loads(r.text)
return res
except RequestException:
print(u'访问错误')
return None
获取article_url代码
def article2(res):
if res and "data" in res.keys():
for a in res.get('data'):
if 'article_url' in a:
art=a.get('share_url')
yield art
获取article_url代码,需要注意,必须用json.loads()对响应进行处理,转换为python对象,这里才可以用get()字典方法提取键的值,用生成器yield来返回所有的article_url,并且用for来打印生成器的返回结果。
获取article_url结果如下:

8.png
请求article_url
def get_detail(url):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
}
r = requests.get(url,headers=headers)
if r.status_code==200:
return r.text
分析详情页
获取详情页图片url代码:
def parse_detail(html):
soup=BeautifulSoup(html,'lxml')
title=soup.select('title')[0].get_text()
print(title)
img=re.compile(r'.*?gallery: JSON.parse\("(.*?)\"\)',re.S)
result=re.search(img,html)
if result:
data = json.loads(result.group(1).replace('\\', ''))
if data and "sub_images" in data.keys():
sub=data.get("sub_images")
for imge in sub:
yield imge.get("url") #用return只能获取组图第一张图片,用生成器yield可获取组图全部图片
详情页有两种,一种是组图,一种是综合显示在一个页面,爬取组图吧,下图这种页面。

9.png
详情页图片链接是在源代码里面,所以直接对上述获取到的article_url发送请求,再从响应表获取图片链接,通过正则表达式获取红色方框的内容,即{}以及{}里面的内容,但是正则获取的结果为json格式,需要对结果进行解析,最后提取里面的图片url。

13.png
解析后输出如下图,此时直接提取图片url即可,这里提取图片url和上面提取article_url方法是一样的:

11.png
从上图发现,图片url是在sub_images键的值里,而sub_images的值是在一个列表里面,列表里面有多个字典,for循环遍历每个字典,get("url")即可获取每个字典里的图片url。
打印如下:

12.png
通过上述代码,可以成功爬取到图片,后面的步骤是构造一个offset数组,遍历AJAX请求里面的offset参数,再请求上述获取的图片url,把请求响应写入到文件,图片是二进制,用xxx.content。
保存图片代码
def imges(url):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
}
try:
r = requests.get(url,headers=headers)
if r.status_code == 200:
res=r.content
file_path = '{0}/{1}.{2}'.format(os.getcwd(),md5(res).hexdigest(),'jpg')
f=open(file_path,'wb')
f.write(res)
except RequestException:
pass
整体代码:
添加了进程池,并且把生成的数组传递给函数article1()的参数,如果想要获取更多图片,把bb参数值改为更大即可,如把20改为100,如果爬取不了,替换一下headers里面的Cookie,脚本运行之后,会把图片下载到当前目录。
import requests
from urllib.parse import urlencode
import json
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
import re
import os
from hashlib import md5
from multiprocessing.pool import Pool
def article1(offset):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
"referer": "https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D%E7%BE%8E%E5%A5%B3",
"x-requested-with": "XMLHttpRequest",
}
params = {
'aid': 24,
'app_name': 'web_search',
'offset': offset,
'format': 'json',
'keyword': '街拍美女',
'autoload': 'true',
'count': 20,
'en_qc': 1,
'cur_tab': 1,
'from': 'search_tab',
'pd': 'synthesis',
'timestamp': 1557973762622,
}
url = 'https://www.toutiao.com/api/search/content/?'+ urlencode(params)
try:
r = requests.get(url,headers=headers)
if r.status_code == 200:
res=json.loads(r.text)
return res
except RequestException:
print(u'访问错误')
return None
def article2(res):
if res and "data" in res.keys():
for a in res.get('data'):
if 'article_url' in a:
art=a.get('share_url')
yield art
def get_detail(url):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
}
r = requests.get(url,headers=headers)
if r.status_code==200:
return r.text
def parse_detail(html):
soup=BeautifulSoup(html,'lxml')
title=soup.select('title')[0].get_text()
print(title)
img=re.compile(r'.*?gallery: JSON.parse\("(.*?)\"\)',re.S)
result=re.search(img,html)
if result:
data = json.loads(result.group(1).replace('\\', ''))
if data and "sub_images" in data.keys():
sub=data.get("sub_images")
for imge in sub:
yield imge.get("url")
def imges(url):
headers = {
"cookie": "csrftoken=957e945bd7c9021add628d4e07c2f1ad; tt_webid=6690773796519233036; s_v_web_id=2d44edde23ebadf3eb32788eff6cf14a; __tasessionId=xcospbenc1558090235428",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
}
try:
r = requests.get(url,headers=headers)
if r.status_code == 200:
res=r.content
file_path = '{0}/{1}.{2}'.format(os.getcwd(),md5(res).hexdigest(),'jpg')
f=open(file_path,'wb')
f.write(res)
except RequestException:
pass
def main(offset):
html=article1(offset)
p=article2(html)
for g in p:
h=get_detail(g)
for j in parse_detail(h):
imges(j)
if __name__=='__main__':
aa=0
bb=60
pool = Pool()
num = ([x * 20 for x in range(aa,bb + 1)])
pool.map(main,num)
pool.close()
pool.join()

运行效果:

第一个是未添加进程池,第二个是添加进程池的运行结果。


未添加进程池


添加进程池