前面三篇文章介绍了如何利用Headers模拟浏览器请求,如何嵌套For循环抓取二级页面。但针对的都是Html文件数据,这一篇我们来看一下另外一种情况的数据以及更加复杂的Headers模拟。
案例是拉勾网(一个招聘网站)抓取某个公司全部招聘信息,然后分析中大型人工智能公司的人才需求分布情况。
这次我们使用Anaconda的Jupyter Notebook。
Anaconda安装教程
1. 理解页面打开这个页面,这是思必驰科技(一家专注于人工智能语音技术的科技公司)在拉勾网的全部招聘职位列表。
思必驰招聘职位
我们可以看到共有47个招聘职位。但是,如果我们【右击-查看网页源代码】,然后【Ctrl+F】搜索第一个职位的名称“运维技术专家”却什么也搜不到,实际上整个页面只有600行左右,并没有包含任何职位信息。
数据不在请求的Html文件里面,数据在哪?
这几年的网站很多都采用了类似游戏的模式:你打开游戏软件的时候,本机电脑里面没有任何玩家信息,但是游戏软件启动后会向服务器请求数据(而不是Html文件),拿到这些数据之后,游戏软件就把各种在线玩家数据显示在屏幕上,让你能够看到他们。
换成网页就是:你刚打开网页的时候,请求的Html文件没有数据,但是网页在浏览器运行之后,网页自己就会向服务器请求数据,网页拿到数据之后,它就会把各种数据填充到页面上,你就看到了这些数据,——但这些数据并不是像以前那样直接写在html文件里的。
动态填充数据页面流程
这些能够动态请求数据和填充数据的代码就是Html网页内运行的Javascript脚本代码,它们可以做各种事情,尤其善于玩弄数据。
JS(Javascript)从服务器获取的数据大多是json格式的,类似下面这种对象(Python里面也叫dict字典),也有xml格式的,这里暂时用不到就不介绍了。
data={ 'title':'内容标题', 'text':'文字内容'}这个格式看上去比html一堆尖括号标记看上去舒服多了。但如何拿到这个数据呢?
2. 理解数据请求Request我们知道Elements面板显示了所有标记元素,而Network面板显示了所有浏览器发出的请求Request,既然JS是向服务器发出请求的,那么就一定会在Network面板留下痕迹。
还是刚才的页面,【右键-检查】切换到Network面板,点击红色小按钮清空,然后点击上面的第2页按钮,查看Network里面的变化。
Network查看JS的xhr请求
我们注意到searchPosition.json这行,它的类型(Type)是xhr,数据请求都是这个类型的。
点击searchPosition.json可以看到这个请求的详细信息。
Headers详细信息
和之前的稍有不同,它没有Parameters数据(因为地址栏没有?aaa=xxx&bbb=yyy这类结尾了),但是多了Form Data表单数据,其实和Parameters作用相同,就是向服务器说明你要哪个公司(companyId)的数据、第几页(pageNo)、每页多少个职位(pageSize)等等。
再点击上面的【preview】预览,可以看到这个请求实际获得了什么数据:
数据结构预览
如图,小三角一路点下去,就能看到这个数据实际和页面展示的职位列表是一一对应的。所以我们只要拿到这个数据就OK了!
3. 发送数据请求上面看到,我们需要的数据都在searchPosition.json这个Request请求里面,【右键-Copy-Copy link address】复制请求地址。
复制请求地址
打开Notebook,新建Python 3文件,粘贴过去。
#单元1url='https://www.lagou.com/gongsi/searchPosition.json'
向这个地址发送请求:
#单元2import requests jsonData=requests.get(url) print(jsonData.text)
全部运行后得到下图结果,我们的爬虫请求被服务器识别了!
直接发起数据请求失败
并不是所有数据请求都会被识别,拉勾网服务器做了这方面的检测,有些网站就没有检测机制,可以直接获取有效数据。
回顾上面截图的浏览器Request请求的Headers信息,实际上浏览器发送请求的时候还携带了很多Request headers(包含cookie),以及Form data数据(对应我们以前提到过的Parameters信息)。
4. 添加params和headersparams就是Form Data,从浏览器的Network面板直接手工复制,然后修改成为Python的字典对象(就是大括号包含的一些属性数据),注意都要加上引号,每行结尾有逗号。
header可以用右键searchPosition.json然后【Copy-Copy Request headers】复制到,但是注意这个字符很多而且换行,所以要用三个单引号才能包括起来。
注意!这里我删除了其中一行Content-Length: 86,因为在发送Request请求的时候Python会自动计算生成Content-Length数值(不一定是86)。如果这里不删除就会导致重复引发错误。
修改单元1的代码:
#单元1url='https://www.lagou.com/gongsi/searchPosition.json'params={ 'companyId': '94', 'positionFirstType': '全部', 'schoolJob': 'false', 'pageNo': '2', 'pageSize': '10'}
headers='''
POST /gongsi/searchPosition.json HTTP/1.1
Host: www.lagou.com
Connection: keep-alive
Origin: https://www.lagou.com
X-Anit-Forge-Code: 38405859
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: application/json, text/javascript, **;q=0.8
...
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
'''这里的jobheader是给二级页面使用的。你必须复制自己浏览器https://www.lagou.com/jobs/5151679.html?source=pl&i=pl-6页面的Request请求5151679.html?source=pl&i=pl-6的信息header信息,我这里只是示意,不能直接复制使用。
#cell-2def str2obj(s,s1=';',s2='='):
li=s.split(s1)
res={} for kv in li:
li2=kv.split(s2) if len(li2)>1:
res[li2[0]]=li2[1] return res
headers=str2obj(headers,'n',': ')
jobheaders=str2obj(jobheaders,'n',': ')这里只是最后一行,也转化jobheaders对象。
#cell-3import jsonimport requestsimport timefrom bs4 import BeautifulSoup
hud=['页数','职位','薪酬','学历','经验','描述']def getJobs(compId=94,school='true',pageCount=1):
for i in range(1,1+pageCount):
params['pageNo']=str(i)
params['companyId']=compId
params['schoolJob']=school
params['pageNo']=i
jsonData=requests.get(url,params=params,headers=headers)
data=json.loads(jsonData.text) #print(json.dumps(data,indent=2,ensure_ascii=False))
jobs=data['content']['data']['page']['result']
for job in jobs:
jobli=[str(i)]
jobli.append(job['positionName'])
jobli.append(job['salary'])
jobli.append(job['education'])
jobli.append(job['workYear'])
#请求二级详情页面
pid=job['positionId']
joburl='https://www.lagou.com/jobs/'+str(pid)+'.html'
jobhtml=requests.get(joburl,headers=jobheaders)
jobsoup= BeautifulSoup(jobhtml.text, 'html.parser')
desc=jobsoup.find('dd','job_bt').div.text
desc=desc.replace('n','')
jobli.append(desc)
time.sleep(1)
print('t'.join(jobli))
time.sleep(1)这里没有直接使用,而是def了一个函数getJobs,带有三个参数compId公司序号,school是否社招,pageCount一共有多少页。
#cell-4print('t'.join(hud))
getJobs(94,'false',5)启动。
本篇小节页面可以不直接包含数据,而是通过运行Javascript代码,从服务器重新获取数据,再填充到页面上。
任何向服务器发起的请求都可以在Network面板找到信息,带了哪些参数params(Form Data),带了什么样的headers,等等
json数据和字典对象用起来一样,从Request获取的文本text数据需要用json.load转换一下,然后就可以用shuju['aa']['bb']的方法一层层找到我们需要的信息
作者:zhyuzh3d
链接:https://www.jianshu.com/p/9de3be54abc1



