从面试题谈起
谈到这个项目的爬虫,就要从一道面试题谈起了。
这道面试题是:
请写一个爬虫从网址 http://zxgk.court.gov.cn/shixin/,检索被执行人姓名:"阿里",并填入验证码后查询,抓取结果列表,与对应的详情,并将内容导出为csv。
根据这道面试题的情境,进入到地址http://zxgk.court.gov.cn/shixin/,在“被执行人姓名/名称”后面的输入框中输入“阿里”,再输入验证码,点击红色的“查询”按钮。如下图所示。
现在需要抓取的是查询后的结果数据,如下图所示。
问题的分析
首先必须明确这些数据是点击查询后出现的动作,可以先审阅代码,在”查询"按钮的地方右键”查看元素",找到点击这个按钮后发生了什么样的动作.如下图所示。
从图形反蓝处可以看到html代码的button元素在onclick方法中有一个search()方法.这个search方法就应该是查询的动作.可以通过查看网页源码找到search方法.
在整个网页空白处右键"查看页面源代码",从”源代码"中搜索出search方法,看一下search方法实现逻辑.如图所示.
从图中,可以看到search方法中有一个ajax的请求,ajax请求的url地址可以看到是searchSX.do。这个地址可以从网页“查看元素”的“Network”或网络中可以查看一下这个请求最终的响应结果是什么样的数据。如下图所示。
由上图中可以看到左边接口地址searchSX.do,对应的右边的响应结果是阿里最终的查询结果。通过这样的分析,以这里只需要知道接口请求的请求头中需要哪些信息即可。如图所示。
如上图所示,在右边的窗口中点击:“消息头”,就可以看到请求头中的相关内容。在请求头中有很多项。
第一项:
Acceptapplication/json,text/javascript,*/*;q=0.01
这是Accept客户端能够接受的MIME的类型。在爬虫请求时可以忽略不设置。
第二项:
Accept-Encoding:gzip, deflate
这是客户端允许的编码,也可以在爬虫请求时忽略不设置。
第三项
Accept-Language:zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
这是客户端允许的语言,一般在中国,都是简体中文,爬虫请求时也可以忽略不设置。
第四项
Connection:keep-alive,
这是保持连接,爬虫爬取时也可以忽略不设置。
第五项:
Content-Length:114,
这是内容的长度,针对本次爬取,爬取可以不设置这个选项。
第六项:
Content-Type:application/x-www-form-urlencoded; charset=UTF-8,
这句标志内容类型,针对本次爬取,爬取时也可以不设置这个选项。
第七项:
Cookie:JSESSIONID=209355FD785AD8D13ED3CC281CF82703; _gscu_15322769=97140202rxdu1031; Hm_lvt_d59e2ad63d3a37c53453b996cb7f8d4e=1605270782; _gscs_15322769=05270772nwfrfu31|pv:4; _gscbrs_15322769=1; Hm_lpvt_d59e2ad63d3a37c53453b996cb7f8d4e=1605271679,
这句是访问这个网站时产生的,这一项必须包括在内,不然爬虫可能爬取不到相关的内容。
第八项:
Host:zxgk.court.gov.cn
主机名字,这个选项在爬取时可以不设置这个选项。
第九项:
Origin:http://zxgk.court.gov.cn,
这个选项爬取时也可以不设置。
第十项:
Referer:http://zxgk.court.gov.cn/shixin/,
如果目标爬取的网站做过盗链的条件下,这个选项是必须要进行设置的.
第十一项:
User-Agent:Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0,
爬取网站时模拟浏览器的客户端动作,这个选项有助于防止服务器进行反爬,也决定了这个选项是必须要进行设置的.
通过以上的分析,请求头必须有的选项定义headers字典如下.
headers={
“User-Agent”:“Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0”,
“referer”:“http://zxgk.court.gov.cn/shixin/”,
“cookie”:“JSESSIONID=209355FD785AD8D13ED3CC281CF82703; _gscu_15322769=97140202rxdu1031; Hm_lvt_d59e2ad63d3a37c53453b996cb7f8d4e=1605270782; _gscs_15322769=05270772nwfrfu31|pv:4; _gscbrs_15322769=1; Hm_lpvt_d59e2ad63d3a37c53453b996cb7f8d4e=1605271679”
}
接下来再点击“查看元素”中“network”或“网络”选项卡的右边“请求”的标签,如下图所示。
从图中可以看到,请求的表单数据中是包括了如下的字典参数,可以用params变量来保存这个字典变量参数。
params={
“pName”:“阿里”,
“pCardNum”:“”,
“pProvince”:“0”,
“PCode”:“wxab”,
“captchaId”:“oQDKheA200LTk4fC3PImakpFcqhbeiig”,
“currentPage”:“1”
}
在以上这个字典参数中,pName就是搜索的关键词,pCode就是对应的验证码,pCardNum、pProvince这两个参数不起什么作用,currentPage是页码的问题,关键是captchaId这个参数到底是个什么东西是值得研究的问题。
针对于capchaId的研究只能针对源码进行。通过“查看网页源代码”,可以看到如下图所示的代码。
从上面截取的代码上看,是能够找到captchaId,并且capthaId的值就是uuid的值进行了repace()方法的替换得到了.那么uuid是如何产生的呢?就是function getNum()中的方法得到了.只要把function getNum()的方法用Python的语言写出来就可以了.
Python语言改写前端函数
function getNum()中的方法是前端的语言,把前端转成Python语言可以遵循以下的原则.
(1)把function字母改成def。
(2)把var赋值语句中的var去掉。
(3)把for循环的格式如for(var i=0;i<32;i++)改成for i in range(32)
(4)把每条前端语句中的{}改成冒号(:),同时应用缩进结构。
(5)把每条前端语句后面的分号(;)去掉,python语句后不需要添加任何符号。
这样,就可以把一段function getNum()前端实现的方法改写成Python语言实现的方法。
function getNum() {
var chars = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ];
var nums = "";
for (var i = 0; i < 32; i++) {
var id = parseInt(Math.random() * 61);
nums += chars[id];
}
return nums;
}
具体Python语言改写后的代码如下。
def getNum():
chars = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z' ]
nums = ""
for i in range(0,32):
id = int(random.randint(0,61))
nums+=chars[id]
return nums
这样改写后的代码可以获取到通过这样的接口就可以把这样的uuid对应服务器指定的验证码,接口如下:
captchaNew.do?captchaId=" + uuid.replace("-", "")+ "&random=" + randomNumber);
以上代码是前端的体现,uuid.replace(“-”,””) 表示把 uuid中的减号替换成空。这里我实在是不明白,明明uuid中没有减号,还要替换成空,可能是代码中故意迷惑人的,对那些不懂代码的人进行一些思路上的骚扰。实际上就是captchaId的值就是getNum()的值。 后面跟上一个随机数据就可以实现验证码的请求,请求验证码后就可以进行请求表单数据中验证码的填写及 captchaId参数的传递了。
Python实现爬取中国执行信息公开网
具体实现代码如下:
import requests
import random
from io import StringIO
from PIL import Image
import json
headers = {
"Cookie":"JSESSIONID=A0053804D456749B5C08F489E3F1D9C2; _gscu_15322769=63264781iz6igj44; _gscbrs_15322769=1; Hm_lvt_d59e2ad63d3a37c53453b996cb7f8d4e=1563264782; Hm_lpvt_d59e2ad63d3a37c53453b996cb7f8d4e=1563264782; _gscs_15322769=63264781zftbxy44|pv:3",
"Host":"zxgk.court.gov.cn",
"Origin":"http://zxgk.court.gov.cn",
"Referer":"http://zxgk.court.gov.cn/shixin/",
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"
}
def getNum():
chars = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z' ]
nums = ""
for i in range(0,32):
id = int(random.randint(0,61))
nums+=chars[id]
return nums
captchaId=getNum().replace("-","")
print(captchaId)
randomNumber =str(random.random())
uuid = str(getNum())
imgs="http://zxgk.court.gov.cn/shixin/captchaNew.do?captchaId="+str(captchaId)+"&random="+randomNumber
print(imgs)
rims=requests.get(url=imgs,headers=headers).content
with open("a.png","wb") as f:
f.write(rims)
pcode=input("请输入验证码:")
data = {
'pName': '阿里',
'pProvince': '0',
'pCode':pcode,
'captchaId':captchaId,
'currentPage': '1'
}
response = requests.post(url='http://zxgk.court.gov.cn/shixin/searchSX.do',headers=headers,data=data)
print(response)
html = response.content.decode("utf8")
html=json.loads(html)
print(html[0]["result"])
这段代码的最终运行结果如下。
从运行结果上看,我们已经抓到了阿里的运行结果。