前言
因为公众号每次都需要图片才能群发,但人为一张张下载又很傻,所以想到用爬虫去爬取。本文旨在总结下一个简单的爬虫步骤,加深自己的印象,如有表达不准确的地方望指出,一起学习。本文爬虫使用 python 语言,基于 cookie 的爬取,所爬取的图片来自于 pixabay 网站。
爬虫步骤
1. 目的 :
首先需要明确爬虫的目标是什么?因为公众号规定最大上传 5M 内的图片,所以我的目标是范围内的最高分辨率的图片。
2. 起始 url:
接着要明确你爬取的起始链接,也就是你一开始请求的网页,这个网页应该具有你想要的东西。我选择的是 pixabay 的搜索页面https://pixabay.com/zh/images/search/
,它内容如下:
该页面的图片便是我们想要的东西。接着发现该页面存在 下一页 按钮,可以缓一口气了,还好不是动态加载页面,点击下一页,观察 url 的变化,可以发现第二页的 url 结尾多了 ?pagi=2
,多验证几个,可以总结出起始页面为https://pixabay.com/zh/images/search/?pagi=1
, 更改最后的数字可以实现切换页数功能。
3. 分析页面信息
右键检查 进入开发者模式,最上方需要关注 element 和 network,其中 element 是请求 url 后 response 的正文内容,network 是请求 url 后的加载过程,可以查看具体的请求头与响应头以及其他参数。
先选任意一张图片 右键检查,如下:
可以发现里面有很多链接,随便打开一个都是这张图片,只是 size 不同,很明显这些图是缩略图,如果对图片分辨率不做要求,我们可以直接爬取这些链接并下载,即可。但是我的目标是高画质的图片,所以需要继续寻找高画质图片的链接才行。
点开一张图,重复上面过程,发现这张图也只是分辨率高了一点而已,不满足要求。接着目光移到免费下载按钮上,可以发现其中 1920*1279 是满足我们要求的最高分辨率图像:
鼠标放“查看”上,右键检查:
其中 href 字段后面就是目标地址,将 href 接在首页地址后,就形成了一个完整的高清图片请求链接,https://pixabay.com/zh/images/download/landscape-4508444_1920.jpg
。
接着需要做的就是 用代码去请求这个链接,并下载服务器返回响应体中的 content。
4. 整理思路:
(1)请求起始 base_url,获取 img 字段中的图片名称 name;
(2)将获取的名称组合成一个完整链接 download_url;https://pixabay.com/zh/images/download/imgname.jpg
;
(3)请求上述连接,并将响应的 content 进行下载;
(4)修改起始 url 中的页数,并重复 2 - 3 步
代码实现
1. 请求起始 url,并获取图片名称:
import re #正则表达式
import requests #请求 url
from lxml import etree #解析起始 url 正文内容
#get 请求起始链接,并返回正文内容
html = requests.get(base_url).text
#解析正文内容
tree = etree.HTML(html)
#提取缩略图链接,我选择的是 data-lazy 字段
img_name = tree.xpath('//img/@data-lazy')
#提取图片名字构成高分辨率图片链接
img_url=[]
#月 - 日 - 时 - 分,正则表达式匹配,只保留图片名
reg = re.compile(r"/[0-9]{2}/[0-9]{2}/[0-9]{2}/[0-9]{2}/(.*)__")
#这是一个页面的所有图片链接
for i in img_name:
name=re.findall(reg, i) #提取名字,name 是一个列表
#组合高分辨率图片链接, 如果不加 1920,可获取原始图片,一般在 5M 以上
img_url.append(download_url+name[0]+"_1920.jpg")
2. 请求下载 url,并将响应内容进行下载:
# 保存路径
img_path = img_root+name+".jpg"
#请求高清图片
img = requests.get(download_url)
#将响应的内容进行下载,下载其实就是写操作
with open(img_path, 'wb') as f:
f.write(img.content)
3. 循环请求 :
将上面的两个模块放入 for 循环中,即可。
问题分析
上面基本上就是一个简单爬虫的结构,既不用模拟登录,也不用模拟动态请求。虽然结构正确,但是按照上面代码进行爬取,是爬不到任何东西,因为你会被阻挡在下面的界面,而你 下载的图片内容只是它的文本内容:
其实可以模拟这个过程,将 download_url 从 无痕模式 和正常模式 下打开,就会发现在正常模式下可以看图片,而在无痕模式下出现的是上图界面。出现这个差别,原因出自于 cookie。
cookie 与 session
1. 概念 :
由于 HTTP 协议是 无状态 协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是 session,其中 session 是保存在 服务端 的,有一个唯一标识。举个栗子:
当你进行购物车下单时,由于 HTTP 协议无状态,其并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的 session 来标识这个用户,并且跟踪用户,这样才知道购物车里面有什么。
那服务端又如何识别特定的客户呢 ?答案就是 cookie。每次 HTTP 请求的时候, 客户端 都会发送相应的 cookie 信息到服务端。实际上大多数的应用都是用 cookie 来实现 session 跟踪的,第一次创建 session 的时候,服务端会在 HTTP 协议中告诉客户端,需要在 cookie 里面记录一个 session ID,以后每次请求把这个会话 ID 发送到服务器,就能确定是哪个客户端。
2. 作用 :
如上所说,cookie 可以用来让服务端“认识”客户端。除此之外,cookie 还可以用来跳过登录环节,再举个栗子:
设想你第一次登录一个网站,输入账号密码后,进入它的主页,此时你关掉页面并重新访问一次,你会发现这次不用输入账号密码,直接进入了主页。
你一定经历过上述场景,原因就是,浏览器将这个登录信息写到 cookie 里,并存储在了本地磁盘。下次访问网页时,网页脚本就可以读取这个信息,并自动帮你把用户名和密码填写了。这也是 cookie 名称的由来,给用户的一点甜头。
3. 小结 :
(1)session 是 服务端 保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
(2)cookie 是 客户端 保存用户信息的一种机制,用来记录用户的一些信息,也是实现 session 的一种方式。
总结
1. 怎么获取 cookie:
从正常模式下请求 download_url(提前打开开发者模式),查看 network 下的请求过程:
上图左边从上到下就是请求的顺序,第一个是请求的 download_url,第二个是返回的图片内容。上图右边的 response headers 是响应体,里面有返回内容的一些信息,如 content-length 等,当 content-length(正文长度)为 0,说明没有请求到数据。requests headers 是请求体,是请求页面需要发给服务器端的信息,其中就包含cookie,以及爬虫经常用到的 user-agent。
大致的请求过程如下:浏览器提交携带 cookie 的请求体 -> 服务端接收后通过 cookie 确定 sessionid -> 返回响应体 -> 通过响应体中给的 location 字段重新请求 -> 服务端返回图片内容 -> 浏览器接收后显示。
2. 怎么携带 cookie 请求:
#yourcookie = {"xxx":"sdsaf"}
html = requests.get(download_url, cookies = yourcookie)
3. 不足与解决方向 :
cookie 请求还是存在 局限性:
- 同一 cookie 多次访问服务器时,服务器会进行重新验证,判断是否是爬虫访问。这个很好识别,一般爬虫对服务器的访问呈线性关系。针对此,可以通过睡眠时间变化来模拟人为访问,但当睡眠时间过长又会使得爬虫效率太低。
- 使用 cookie 爬虫,就不能通过修改 user-agent 或 ip 代理来模拟不同人的访问,因为 cookie 是具有唯一性的。
因此本文代码就出现弊端,抓取十几次就需要人为进行验证,并重新抓取。但就目前爬取的图片数量已经够公众号使用,所以有机会再解决吧。解决方向 - 可以模拟人为点击认证吧。完整代码可以访问 github
)获取。
最后展示下爬取的效果:
【关注公众号DoCode,每日一道 LeetCode,将零碎时间利用起来】