这两天呢,一直没有更新,因为组里面接了一个职位分析项目爬取的任务,这两天也一直在忙着爬这个,没有时间更新。 在写猎聘代码的时候,因为是好久之前就写了的,编的是个组合代码,运行是没有问题,我们那代码就连页码都不用输入,就只用输关键词,把要爬的地区,改一下就代码就能定位到有多少页爬多少页ok。
说的是没有问题,不过后面操作起来就存在问题了,有的页面他登录和不登录显示的页码是不一样的,我们用组合代码,这就无形之间造成了数据爬取的不完整,为了解决这个问题我也在csdn找了很多的方法吧,但实际没有得到很有效的解决,还是爬了几个数据就输出空了。
不过我竟然能发这个博客肯定是找到了解决方法的,其实我也没有想到会这么简单,下面跟着我一起来顺利爬取猎聘网的数据吧。
我要吐槽一下这个猎聘网那个脑洞,我在没登录第一次观察网页的时候,发现他最多的页数能看到10页,登录了是30页,这没什么,可以理解哎,不过最那个什么的是,发现他我爬多了,就输出空了,我去看网页,登录了还只能显示8页,我退了再看,他有能显示10页了,嗯,有点蒙,登录了还没有没登录的多,抱着实验的态度,我又把组合代码拆开了,你说最多能爬30,我就把30页的网址给你编出来,我还不是爬取页码编辑了,没想到还成功了。
第一组代码:引库,这里主要用到了正则,时间休眠,requests,pandas,以及正则提取的库
import requests,re,time,pandas as pd from lxml import etree第二组代码:编写headers,是常规字典的编辑,键值对来做的。
h={'Cookie':'无', 'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'}第三组代码:编写你需要爬取网址改变的地方,这里主要是关键词、地区等信息,我这里用的是大数据作为关键词,粤港澳地区做的地区,是个列表形式,如果爬一个地方改一下代码就行了。
word='大数据' dqs=['320','330','050020','050090','050140','050050','050060','050040','050130','050150','050120']第三组代码:构建外部网址格式,并存入列表。
urla=[] for dq in dqs: for i in range(0,31): urla.append('https://www.liepin.com/zhaopin/?compkind=&dqs='+dq+'&pubTime=&pageSize=40&salary=&compTag=&sortFlag=15°radeFlag=0&compIds=&subIndustry=&jobKind=&industries=&compscale=&key='+word+'&siTag=LGV-fc5u_67LtFjetF6ACg%7E_FrslumzzaHrHe3aSW0VTQ&d_sfrom=search_prime&d_ckId=489b14685f420f9c47017c369cb1c6f9&d_curPage=0&d_pageSize=40&d_headId=489b14685f420f9c47017c369cb1c6f9&curPage='+str(i))第四组代码:构建空列表,用来存储详细页网址。
wz=[]第五组代码:设置循环遍历所有外部网址,xpath爬取详细页网址并存入空列表中。#打印行数是为了看每一页累加的详细页网址的个数。
for u in urla: q=requests.get(u,headers=h) time.sleep(1.2) jx=etree.HTML(q.text) wz=wz+jx.xpath('//div[@class="sojob-item-main clearfix"]//h3/a/@href') print(len(wz))第六组代码:处理爬取到的详细页网址的编辑问题。
for s1 in range(len(wz)): wz[s1]=re.sub('https://www.liepin.com','',wz[s1]) for s2 in range(len(wz)): wz[s2]='https://www.liepin.com'+wz[s2]第七组代码:建立空列表,用来存储详细页网站爬取的信息。
name=[] company=[] salary=[] local=[] yaoqiu=[] walfare=[] gangwei=[] gongsi=[] hangye=[] rongzi=[] guimo=[]第八组代码:爬取详细页信息并写入列表,打印是为了帮助我们看爬了多少组数据。
for j in wz1: ress=requests.get(j,headers=h,timeout=3.0) htt=etree.HTML(ress.text) p1=''.join(htt.xpath('//div[@class="title-info"]//h1/text()')) if p1=='':#职位名称 p1=''.join(htt.xpath('//div[@class="title-info "]/h1/text()')) name.append(p1) else: name.append(p1) p2=''.join(htt.xpath('//div[@class="title-info"]//a/text()')) if p2=='':#公司名称 p2=re.sub('\s','',''.join(htt.xpath('//div[@class="title-info "]/h3/text()'))) company.append(p2) else: company.append(p2) salary.append(''.join(htt.xpath('//div[@class="job-note"]//p/text()[1]')))#薪资 p3=''.join(htt.xpath('//p[@class="basic-infor"]//a/text()')) if p3=='':#工作地点 p3=''.join(htt.xpath('//p[@class="basic-infor"]/span/text()')) local.append(p3) else: local.append(p3) p4='|'.join(htt.xpath('//div[@class="job-qualifications"]/span/text()')) if p4=='':#要求 p4='|'.join(htt.xpath('//div[@class="resume clearfix"]/span/text()')) yaoqiu.append(p4) else: yaoqiu.append(p4) walfare.append('|'.join(htt.xpath('//div[@class="comp-tag-box"]//span/text()')))#福利 p5=''.join(htt.xpath('//div[@class="job-item main-message job-description"]/div/text()')) if p5=='': p5=re.sub('\s','',''.join(htt.xpath('//div[@class="job-main job-description main-message"]/div/text()'))) gangwei.append(p5) else: gangwei.append(p5) gongsi.append(''.join(htt.xpath('//div[@class="info-word"]/text()')))#公司介绍 hangye.append(''.join(re.findall('行业:<a.*?>(.*?)</a>',ress.text,re.S)))#公司行业 rongzi.append(''.join(re.findall('<li>融资:(.*?)</li>',ress.text,re.S)))#融资 guimo.append(''.join(re.findall('<li>公司规模:(.*?)</li>',ress.text,re.S)))#公司规模 print(len(name)) time.sleep(3)第九组代码:编写字典,转化数据框。
da={'职位名称':name,'公司名称':company,'薪资':[re.sub('\s+','',x) for x in salary],'工作地点':local,'要求':yaoqiu,'福利':walfare,'岗位要求':gangwei,'公司介绍':[re.sub('\s+','',k) for k in gongsi], '公司行业':hangye,'融资':rongzi,'公司规模':guimo} data=pd.DataFrame(da)第十组代码:输出Excel表格或csv文件,这里用Excel为例。
data.to_excel('E:\\猎聘-'+word+'.xlsx')详细页爬取报错处理办法
第一种报错:timeout报错,一般是正常报错 属于访问超时。
处理办法:记住最后打印输出的页码,在之前起一行wz1=wz[这里输最后打印的数字:],把详细页爬取那块的代码,wz换成wz1再把两个先后运行就可以接上了。
另外,在爬取的时候建议做好散热工作,如果经常性报错或发现爬一段后输出空建议更换headers(这个基本上不存在,除非他网址又变化了)
这里主要是一些前期的准备,还没有正式的写代码。
以北京的大数据职位搜索为例,每次翻页记录一次网址,观察他的网址变化。
https://www.liepin.com/zhaopin/?industries=&subIndustry=&dqs=010&salary=&jobKind=&pubTime=&compkind=&compscale=&searchType=1&isAnalysis=&sortFlag=15&d_headId=95dddf8ad7649e1a16d187e43fe61ebc&d_ckId=875882f3cc4739abe08e4c97a6f93da7&d_sfrom=search_prime&d_curPage=2&d_pageSize=40&siTag=LGV-fc5u_67LtFjetF6ACg%7EF5FSJAXvyHmQyODXqGxdVw&key=%E5%A4%A7%E6%95%B0%E6%8D%AE
https://www.liepin.com/zhaopin/?compkind=&dqs=010&pubTime=&pageSize=40&salary=&compTag=&sortFlag=15°radeFlag=0&compIds=&subIndustry=&jobKind=&industries=&compscale=&key=%E5%A4%A7%E6%95%B0%E6%8D%AE&siTag=LGV-fc5u_67LtFjetF6ACg%7EF5FSJAXvyHmQyODXqGxdVw&d_sfrom=search_prime&d_ckId=875882f3cc4739abe08e4c97a6f93da7&d_curPage=0&d_pageSize=40&d_headId=95dddf8ad7649e1a16d187e43fe61ebc&curPage=1
https://www.liepin.com/zhaopin/?compkind=&dqs=010&pubTime=&pageSize=40&salary=&compTag=&sortFlag=15°radeFlag=0&compIds=&subIndustry=&jobKind=&industries=&compscale=&key=%E5%A4%A7%E6%95%B0%E6%8D%AE&siTag=LGV-fc5u_67LtFjetF6ACg%7EF5FSJAXvyHmQyODXqGxdVw&d_sfrom=search_prime&d_ckId=875882f3cc4739abe08e4c97a6f93da7&d_curPage=1&d_pageSize=40&d_headId=95dddf8ad7649e1a16d187e43fe61ebc&curPage=2
总共截了三页的网址,可以发现最开始的和另外两个不一样的地方有点多,那我们就尽量不要在第一页这个网址下编辑规律,那我们第一页的网址怎么弄,多观察一下可以发现有三个参数是会轻易改变,一个就是控制页码的参数curPage=?,一个控制搜索词的参数key=?最后一个是控制地区的参数dqs=?,这个是需要多观察,但也不是很难。
找到这些规律我们就可以编写我们的外部网址的格式了,再把那个网址看一下是不是第一页是page=0。
'https://www.liepin.com/zhaopin/?compkind=&dqs='+dq+'&pubTime=&pageSize=40&salary=&compTag=&sortFlag=15°radeFlag=0&compIds=&subIndustry=&jobKind=&industries=&compscale=&key='+word+'&siTag=LGV-fc5u_67LtFjetF6ACg%7E_FrslumzzaHrHe3aSW0VTQ&d_sfrom=search_prime&d_ckId=489b14685f420f9c47017c369cb1c6f9&d_curPage=0&d_pageSize=40&d_headId=489b14685f420f9c47017c369cb1c6f9&curPage='这里留下了两个可以改变的地方,一个是地区,一个是关键词。
以我爬取粤港澳大湾区的大数据信息为例,这里word='大数据',因为我爬的不单单是一个地区,所以上面的dq实际上充当的是循环遍历的可变元素,那我们就用一个列表把所有的存起来就好。 如果是列表的话,出来是dqs=['320','330','050020','050090','050140','050050','050060','050040','050130','050150','050120']这样的,如果只是一个那就和word的形式一样dq='?',这里还是比较简单的,多个地区的话就后面编写循环遍历就好,关键词基本上就是一个也没有说是爬多个的,那样后期分析也不好分析。
这里也就不多介绍了,csdn里面有很多讲这块的。
我在前面说的,因为有的时候网址欺骗我们,不给我们显示实际的页码,我们就直接暴力爬取目前可观的最多的页码30页,前开后闭,还多执行一点。
urla=[] for dq in dqs: for i in range(0,31): urla.append('https://www.liepin.com/zhaopin/?compkind=&dqs='+dq+'&pubTime=&pageSize=40&salary=&compTag=&sortFlag=15°radeFlag=0&compIds=&subIndustry=&jobKind=&industries=&compscale=&key='+word+'&siTag=LGV-fc5u_67LtFjetF6ACg%7E_FrslumzzaHrHe3aSW0VTQ&d_sfrom=search_prime&d_ckId=489b14685f420f9c47017c369cb1c6f9&d_curPage=0&d_pageSize=40&d_headId=489b14685f420f9c47017c369cb1c6f9&curPage='+str(i))先用url=[]构建空列表,用第一层循环来把地区的列表遍历读入,第二次循环来拿页码。每循环一次第二层循环urla累加一次新的外部网址,这里可以想象自己去商店拿篮子买东西。
执行完可以用print(uela)打印收集到的外部网址,用len(urla)查看外部网址数据量。
因为我只是在详细页网址上爬取数据输出,所以外部网址爬取详细页尤为重要。
headers是requests很重要的一个参数,我用的谷歌,右键检查进入开发者工具,点击Network菜单,刷新页面,一般在第一个Name里面就可以找到我们需要的编辑headers的参数,设置headers是字典形式的,以键值对出现,我比较喜欢添加的就是Cookie和User-Agent。
h={'Cookie':'__uuid=1598794902612.13; __s_bid=4e5e1e512ae57cee47ff3f563e36d3db2db7; need_bind_tel=false; c_flag=b4ff78c06612c39c9b8225ce9f8db985; imClientId=a4fef61bbb19248e57a7af9cc93c2f5c; imId=a4fef61bbb19248e7080b60edf58eb94; imClientId_0=a4fef61bbb19248e57a7af9cc93c2f5c; imId_0=a4fef61bbb19248e7080b60edf58eb94; gr_user_id=6877e7fe-687d-46e8-90c4-e30bd332b093; bad1b2d9162fab1f80dde1897f7a2972_gr_last_sent_cs1=734389e5a21b20811a667e00ad0d9493; grwng_uid=a0f4b15f-9377-4912-b011-3109b29fe8ec; __tlog=1599015686456.30%7C00000000%7C00000000%7C00000000%7C00000000; _fecdn_=1; inited_user=17632554d5c7a0f774ec62a706b54ee6; user_roles=0; new_user=false; bad1b2d9162fab1f80dde1897f7a2972_gr_session_id=8b2ec5a4-bb20-4cc7-9053-4d40c711482e; bad1b2d9162fab1f80dde1897f7a2972_gr_last_sent_sid_with_cs1=8b2ec5a4-bb20-4cc7-9053-4d40c711482e; bad1b2d9162fab1f80dde1897f7a2972_gr_session_id_8b2ec5a4-bb20-4cc7-9053-4d40c711482e=true; UniqueKey=734389e5a21b20811a667e00ad0d9493; lt_auth=vusPOHQCyAmr4yHe2DMP5fkZ2oj7VmqYp3xbhRAJ14W9XPbh4PbmRQ2CprcGxAMhkUh0f8ULNLn3Nev2yHpP7UMSwGmnloCxteW703YeTeFlHuyflMXuqs7QQpUsrXg6ykpgn2si; Hm_lvt_a2647413544f5a04f00da7eee0d5e200=1598802677,1598850169,1599015688,1599038355; user_photo=5d5513d3cd394174d09f1d5907u.png; user_name=%E7%8E%8B%E5%8D%93; fe_se=-1599038463128; __session_seq=62; __uv_seq=62; bad1b2d9162fab1f80dde1897f7a2972_gr_cs1=734389e5a21b20811a667e00ad0d9493; Hm_lpvt_a2647413544f5a04f00da7eee0d5e200=1599038464; JSESSIONID=D6C94B78CDBE22525F0CBAAFE8B6183B; fe_im_socketSequence_0=2_1_1', 'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'}我这个Cookie应该是在详细页里面的找的,自己可以多试一下,能用基本上爬取的话一个就ok。
建立列表是为了存爬取的详细页网址.
wz=[] for u in urlb: q=requests.get(u,headers=h) time.sleep(1.2) jx=etree.HTML(q.text) wz=wz+jx.xpath('//div[@class="sojob-item-main clearfix"]//h3/a/@href') print(len(wz))爬取的时候用的是xpath,需要etree.HTML()进行转换,点位检查: 这里的xpath提取规则也比较好观察,jx.xpath('//div[@class="sojob-item-main clearfix"]//h3/a/@href'),jx.xpath()括号里用引号包住节点与标签,输出最后一个的内容,并且会把所有符合条件的全部提取出来。 当然如果要提取其他的,编写其他的规律就好,其实我不太建议直接开发者工具复制xpath,因为有些可以,有些就需要精准一点。 每次循环中打印详细页网址的数量,是为了观察每一页详细页网址累加后的数据。 因为之前编写的是没有加判断的,所以打印的话会重复,但实际影响不大。
我们用wz(对存详细页网址的列表命名)看一下爬取到的详细页网址。 这个的出现也比较好理解 含有job的是企业招聘,而a的是猎头招聘。 可以发现有一些网址他是不是正规的网址形式,运行会报错,所以我们需要通过正则进行替换操作。
1. 首先把含有job的网址的https://www.liepin.com全部替换为空。
for s1 in range(len(wz)): wz[s1]=re.sub('https://www.liepin.com','',wz[s1])2. 最后把所有的前面加https://www.liepin.com。
for s2 in range(len(wz)): wz[s2]='https://www.liepin.com'+wz[s2]当然这是我比较喜欢的方法,用列表推导式也是可以。如果只需要企业招聘的数据也可以建立判断,只要企业招聘的详细页网址,看自己吧。
在以前的时候,他们两个网站是同样的,不过后面就不一样了,所以这次因为以求数据量多,两个都要,所以也写了判断。
这行代码虽然看起来长,实际上也好理解,就是先提取后面又做了一个去空格操作。
这里,如果直接提取class标签等于"resume clearfix"的div节点下的span节点下的文字的话,会提出多个,如果直接存到列表中的话,会造成数据的不对应,最后转化数据框报错:不一样的长度,所以我们需要把变为一个字符串写入列表,用到了''.join(),这个不仅仅是做刚刚的多变一操作,也可以在没有数据的时候输出'',不会报错。
'|'.join(htt.xpath('//div[@class="resume clearfix"]/span/text()'))这里是举的比较典型的例子来佐证他们网站编辑的不同,但是实际上看自己的需求爬取。
这里没有什么说的,前后对应就好,不要多次运行,否则丢数据。
name=[] company=[] salary=[] local=[] yaoqiu=[] walfare=[] gangwei=[] gongsi=[] hangye=[] rongzi=[] guimo=[] url=[]这块与遍历外部网址爬取详细页网址类似
for j in wz1: ress=requests.get(j,headers=h,timeout=3.0) htt=etree.HTML(ress.text) p1=''.join(htt.xpath('//div[@class="title-info"]//h1/text()')) if p1=='':#职位名称 p1=''.join(htt.xpath('//div[@class="title-info "]/h1/text()')) name.append(p1) else: name.append(p1) p2=''.join(htt.xpath('//div[@class="title-info"]//a/text()')) if p2=='':#公司名称 p2=re.sub('\s','',''.join(htt.xpath('//div[@class="title-info "]/h3/text()'))) company.append(p2) else: company.append(p2) salary.append(''.join(htt.xpath('//div[@class="job-note"]//p/text()[1]')))#薪资 p3=''.join(htt.xpath('//p[@class="basic-infor"]//a/text()')) if p3=='':#工作地点 p3=''.join(htt.xpath('//p[@class="basic-infor"]/span/text()')) local.append(p3) else: local.append(p3) p4='|'.join(htt.xpath('//div[@class="job-qualifications"]/span/text()')) if p4=='':#要求 p4='|'.join(htt.xpath('//div[@class="resume clearfix"]/span/text()')) yaoqiu.append(p4) else: yaoqiu.append(p4) walfare.append('|'.join(htt.xpath('//div[@class="comp-tag-box"]//span/text()')))#福利 p5=''.join(htt.xpath('//div[@class="job-item main-message job-description"]/div/text()')) if p5=='': p5=re.sub('\s','',''.join(htt.xpath('//div[@class="job-main job-description main-message"]/div/text()'))) gangwei.append(p5) else: gangwei.append(p5) gongsi.append(''.join(htt.xpath('//div[@class="info-word"]/text()')))#公司介绍 hangye.append(''.join(re.findall('行业:<a.*?>(.*?)</a>',ress.text,re.S)))#公司行业 rongzi.append(''.join(re.findall('<li>融资:(.*?)</li>',ress.text,re.S)))#融资 guimo.append(''.join(re.findall('<li>公司规模:(.*?)</li>',ress.text,re.S)))#公司规模 print(len(name)) time.sleep(3)但是因为两个网站的不同,用到了一些判断,我来解释一个,用p1吧,第一行p1=''.join(htt.xpath('//div[@class="title-info"]//h1/text()'))是第一种网站的规则,第二行if p1=='':即如果p1这个第一个网站的规则提取下来为''那么移步第三行p1=''.join(htt.xpath('//div[@class="title-info "]/h1/text()'))p1现在就变成了第二种网站的规则,运行第四行,name.append(p1)写入操作,else:反之如果p1不等于'',则用第一个网站规则直接进行写入操作。
p1=''.join(htt.xpath('//div[@class="title-info"]//h1/text()')) if p1=='':#职位名称 p1=''.join(htt.xpath('//div[@class="title-info "]/h1/text()')) name.append(p1) else: name.append(p1)print(len(name))跟前面的打印类似,是为了看爬取进度的
键值对的形式需要注意一一对应。
da={'职位名称':name,'公司名称':company,'薪资':[re.sub('\s+','',x) for x in salary],'工作地点':local,'要求':yaoqiu,'福利':walfare,'岗位要求':gangwei,'公司介绍':[re.sub('\s+','',k) for k in gongsi], '公司行业':hangye,'融资':rongzi,'公司规模':guimo} data=pd.DataFrame(da)之后就可以直接存为表格什么的,都可以操作。
不知道是不是我比较幸运,找到了一个不会被封的cookie,目前爬的数据都好着呢,也没有再写一站式组合代码了,如果感兴趣的朋友们可以试着搞一下,看看效果
2020年9月4日写的第一稿