【项目介绍】
现在很多网址都是异步加载的,一般我们浏览一个页面返回的是网页的框架,而内容是用JavaScript渲染的,一般是我们一边拖动内容网页会一边发出异步加载的请求并响应部分内容,这样子我们打开网页时响应速度会快很多,也能减小服务器的压力,使服务器支持更多的并发。但是也因为异步加载的关系,用以往直接请求网页内容的方式是找不到要爬的内容的,这个项目尝试分析今日头条的异步加载方式,爬取头条上的图片。
【项目工具】
Pycharm+selenium(主要用于获取cookies)+requests
【项目流程】
1.分析今日头条加载方式:搜索街拍对应页面的url发现response的是网页的框架,没有返回街拍内容。
切换到XHR,可以看到选中内容返回json数据,里面有街拍的详细内容,并且往下滑动街拍内容,可以看到XHR里有多个相似URL加载出来,对比他们的URL发现主要是offset不同,offset一般指偏移量。
如图,可以判断该URL的param比较好构造,除了offset其他都是固定的,另外timestamp是时间戳,可以不加。
2.发现如果直接爬对应URL,获取到的json数据里data为空,通过网上查找发现需要附带cookie,另外可以发现直接跳转“keyword=街拍”的URL会跳出验证,而从今日头条首页搜索“keyword=街拍”再搜索访问页面就不会出现验证,故判断先找到请求头的cookies才能实现后续访问。
!注意是请求头的cookie,可以用selenium去get_cookies(),也可以直接打开网页复制,之前好几次用了另外获取cookie的方法,得到的都是响应头的cookie,这里存在困惑。
3.找图片。我们发现今日头条街拍的文章分为两种,一种是点击进去图片已经排列好,另一种是类似相册的方式,需要左右点击跳转下一站图片,这两种对应的图片爬取方式是不一样的。
具体分析两种:
这种是图片集的(需要左右点击切换照片),这种如果在json的image_list里是得不到全部图片的,需要访问该文章的链接,在链接里的html里找。
另一种则可以直接在image_list里找:
4.爬取图片
5.存放图片
附完整代码:
import requests,re,os
from hashlib import md5
from selenium import webdriver
def get_cookies(url):
str=''
options = webdriver.ChromeOptions()
options.add_argument('--headless')
browser = webdriver.Chrome(options=options)
browser.get(url)
for i in browser.get_cookies():
try:
name=i.get('name')
value=i.get('value')
str=str+name+'='+value+';'
except ValueError as e:
print(e)
return str
def get_page(offset):
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',
}
url='https://www.toutiao.com/api/search/content/'
try:
r=requests.get(url,params=params,headers=headers)
if r.status_code==200:
print(r.content)
return r.json()
else:
print('requests get_page error!')
except requests.ConnectionError:
return None
def get_images(json):
remove_chars = '[’!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
data=json.get('data')
if data:
for i in data:
if i.get('title'):
title=re.sub('[\t]','',i.get('title'))
title = re.sub(remove_chars, '', title) # 生成目录会有一些限制,比如有英文问号会出错,因此删减一些特殊符号防止出错
url=i.get('article_url')
if url:
r=requests.get(url,headers=headers)#
if r.status_code==200:
images_pattern = re.compile('JSON.parse\("(.*?)"\),\n', re.S)
result = re.search(images_pattern, r.text)
if result:
b_url='http://p3.pstatp.com/origin/pgc-image/'
up=re.compile('url(.*?)"width',re.S)
results=re.findall(up,result.group(1))
if results:
for result in results:
yield {
'title':title,
'image':b_url+re.search('F([^F]*)\\\\",',result).group(1)
#^匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合
}
#yield类似return的作用,不过return一般是返回后停止执行,这个类似迭代器,会继续执行。
else:
images = i.get('image_list')
for image in images:
origin_image = re.sub("list.*?pgc-image", "large/pgc-image",
image.get('url')) # 改成origin/pgc-image是原图
yield {
'image': origin_image,
'title': title
}
def save_image(item):
img_path = 'image' + os.path.sep + item.get('title')
if not os.path.exists(img_path):
os.makedirs(img_path) # 生成目录文件夹
'''
建立保存图片的目录
如果目录不存在,则建立文件目录。
使用os.makedirs(file_path)建立目录,而不是使用os.mkdir(file_path)。
因为mkdir只能建立单级文件目录。
makedirs则能建立多级文件目录,也能建立单级文件目录。
单级文件目录:img
多级文件目录:my/book/img
'''
try:
'''
获得图片名(包含路径)
从图片url中取得图片后缀——jgp,png之类的
os.path.splitext(path)
将文件路径(包含文件名)拆分为:[路径\文件名, 文件后缀]
拼接图片名(包含路径)
filename = 目录路径 +文件分隔符+ 图片名+图片后缀
使用os.sep获得系统文件分隔符,避免不同平台造成不同的文件分隔符。
'''
resp = requests.get(item.get('image'))
if requests.codes.ok == resp.status_code:
file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format(
file_name=md5(resp.content).hexdigest(),#通过哈希函数对每个文件进行文件名的自动生成:MD5加密后进行十六进制转换。
file_suffix='jpg') # 单一文件的路径
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(resp.content)
print('Downloaded image path is %s' % file_path)
else:
print('Already Downloaded', file_path)
except Exception as e:
print(e,'none123')
def main(offset):
a = get_page(offset)
for i in get_images(a):
save_image(i)
cookies = get_cookies('https://www.toutiao.com')
headers = {
'cookie': cookies,
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'x-requested-with': 'XMLHttpRequest',
'referer': 'https://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D',
}
if __name__=='__main__':
#p.map(main,[0]) #之所以不用Pool多进程是因为目前还没有办法实现跨进程共享Cookies
#map(main,[x*20 for x in range(3)]) map没有输出,不知道为什么
for i in [x*20 for x in range(3)]:
main(i)
【参考材料】
图片的存放:https://www.jianshu.com/p/938763947de3
yield的解释:https://blog.csdn.net/mieleizhi0522/article/details/82142856/
程序参考:https://github.com/Python3WebSpider/Jiepai
生成目录去除违规符号:https://blog.csdn.net/u010960155/article/details/90378292