目录
腾讯招聘网职位爬取程序
1.需求分析
腾讯招聘网首页URL:https://careers.tencent.com/search.html
首页与大部分求职网并无太大差别,我们的目的是爬取某个岗位(如运维,设计爬虫程序时会提示输入工作名称)的所有工作岗位信息。
这些信息包括:岗位名称、发起时间、工作地点,工作职责、工作要求。
2.URL分析
1)分析首页URL
首先看到腾讯招聘网的首页:https://careers.tencent.com/search.html,查看源码:
啥也没有,说明所有的招聘信息都是JS嵌进去的,或者说是异步(AJax)获取的。
进入控制台,点击Network,点击XHR,找到两个文件,我们在Query开头的文件中获取到了岗位信息。
下面来分析这一段URL。
在地址栏中输入这一串URL,我的格式之所以这么清晰是以为装了一个叫JSONView的插件(谷歌浏览器插件,安装教程:https://blog.csdn.net/ck784101777/article/details/104291634)
下面来分析这一段URL,此URL有很多参数,我们的目的是尽量的缩短这个URL的长度(不缩短也可以),进过缩短,发现必须保留的参数有timestamp、keyword、pageIndex、pageSize
timestamp:好理解,时间桩,进过我的测试这个时间桩写什么都无所谓,但是必须有,所以我们下面让他为1就可以了。
keyword:搜索的职位名称,为空时代表搜索全部职位,当搜索java工程师时keyword=java工程师
pageIndex:代表页数,一个URL下只有10条职位信息,页数从1开始,我们接下来也要计算某职位的页数,只有这样才能将数据抓全
pageSize:固定值,职位数量,这里是10
经过缩短,这条URL变为(其中keyword和pageIndex是变化的):
2)分析岗位详情URL
这是岗位详细的URL:https://careers.tencent.com/jobdesc.html?postId=1229978126717554688
很明显的可以看出postId决定这一页面显示什么信息,所以我们的目的就是提取postId。而恰好,刚才我们分析首页URL时,页面里面正好有PostId的信息。
3.程序设计思路
1)URL分级
根据刚才对URL的分级,我们可以将首页URL称为1级URL,将工作详情URL称为2级URL,我们要先抓取1级URL,再抓取2级URL。
根据这个思路,我们可以设置两个队列,一个队列用于存放1级URL,一个队列存放2级URL。
2)功能函数:网页请求函数
这个函数用于请求网页内容,使用requests模块的get()方法即可,该函数还需要传入一个参数url。如下
#功能函数:调用requests请求页面 def get_html(self,url): html=requests.get(url=url,headers=self.headers).text return html
3)功能函数:一级URL入队
根据上面所述,我们需要一个函数来把一级页面的URL添加到队列里,但是我们不知道有多少个一级页面,我们需要计算出pageIndex的值。
#功能函数:将url放入队列(一级url) def url_inQueue(self,keyword): count=self.get_pageCount(keyword) for i in range(1,count+1): url=self.index_url.format(keyword,i) self.q1.put(url)
4)功能函数:计算pageIndex
在看到一级URL所展示的内容,我们可以看到有个叫Count的值,这个值就是工作的总数,根据工作的总数,除以每个页面的职位数量(10个),就可以计算出总的页数。
#功能函数:获取工作总页数 def get_pageCount(self,keyword): #keyword是工作的关键字,1代表仅请求第一页(第一页就有总的个数) url=self.index_url.format(keyword,1) html=self.get_html(url) html=json.loads(html) count=html["Data"]['Count'] pagecount=0 #pageSize=10 每页有10条工作信息,除以10获取到总的页数 if count % 10 == 0: pagecount=count // 10 else: pagecount=count // 10 + 1 return pagecount
5)核心函数:解析一级URL
此时我们已经拿到所有的一级URL,下面就要提取PostID,组成二级URL,在把URL插入到二级URL的队列
#核心函数:解析一级url,获取二级url并放入队列 def parse_indexurl(self): #死循环用于阻止线程阻塞 while True: if not self.q1.empty(): url = self.q1.get() html=self.get_html(url) jsonlists=json.loads(html) for i in jsonlists["Data"]["Posts"]: postsid=i['PostId'] #格式化二级url url=self.second_url.format(postsid) #将二级url放入二级url的队列 self.q2.put(url) else: break
6)核心函数:解析二级URL
直接调用get_html()方法拿到页面内容,再将其json化,就可以输出职位的信息。
# 核心函数:解析二级url,获取职位信息 def parse_secondurl(self): # 死循环用于阻止线程阻塞 while True: if not self.q2.empty(): url=self.q2.get() html=self.get_html(url) jsonlist=json.loads(html) items={} # 封装数据 items["RecruitPostName"] = jsonlist['Data']['RecruitPostName'] items["LocationName"] = jsonlist['Data']['LocationName'] items["Responsibility"] = jsonlist['Data']['Responsibility'] items['LastUpdateTime'] = jsonlist['Data']['LastUpdateTime'] print(items) #加锁(防止线程争抢资源),找到一个工作总数加1 self.lock.acquire() self.jobcount+=1 self.lock.release() else: break
4.设置多线程
按照程序执行的步骤:
1)拿到1级URL并放到队列
2)从队列中提取并解析1级URL
3)将解析出的2级URL放入队列
4)解析2级URL并输出信息
这样的执行步骤是单线程的,也就是按顺序执行,程序要等到提取完所有的1级URL再进行2级URL的解析,并且2级URL的解析也是单线程进行解析,如果遇到一个页面需要较长的等待时间,程序会一直等待。
所以我们要将程序做成多线程的,一旦爬到1级URL,就有线程去解析2级URL,整个过程是并行进行的。
多线程代码如下:
# 执行函数:程序执行的入口 def run(self): #获取职位关键词 keyword=input("请输入搜索的职位:") keyword=parse.quote(keyword) #调用入队函数,把一级页面都放入队列 self.url_inQueue(keyword) t1_lists=[] t2_lists=[] # 开启1个线程用于抓取一级页面的url for i in range(1): t=Thread(target=self.parse_indexurl()) t1_lists.append(t) t.start() # 开启大于1个线程用于抓取二级页面的url,缩短抓取时间 for i in range(2): t=Thread(target=self.parse_secondurl()) t2_lists.append(t) t.start() # 阻塞线程 for t in t1_lists: t.join() for t in t2_lists: t.join()
5.程序代码
import requests
import time
import json
from UserAgent import get_UserAgent
from urllib import parse
from queue import Queue
from threading import Thread,Lock
class TecentJobs_spider(object):
#初始化函数
#定义一二级页面URL格式
#定义headers
#定义url队列
#定义一个整数用于记录工作的个数,并赋予锁
def __init__(self):
self.index_url="https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1582079472895&keyword={}&pageIndex={}&pageSize=10"
self.second_url="https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1582086419823&postId={}&language=zh-cn"
self.headers={
'User-Agent':get_UserAgent(),
}
self.q1=Queue()
self.q2=Queue()
self.jobcount=0
self.lock=Lock()
#功能函数:调用requests请求页面
def get_html(self,url):
html=requests.get(url=url,headers=self.headers).text
return html
#功能函数:获取工作总页数
def get_pageCount(self,keyword):
#keyword是工作的关键字,1代表仅请求第一页(第一页就有总的个数)
url=self.index_url.format(keyword,1)
html=self.get_html(url)
html=json.loads(html)
count=html["Data"]['Count']
pagecount=0
#pageSize=10 每页有10条工作信息,除以10获取到总的页数
if count % 10 == 0:
pagecount=count // 10
else:
pagecount=count // 10 + 1
return pagecount
#功能函数:将url放入队列(一级url)
def url_inQueue(self,keyword):
count=self.get_pageCount(keyword)
for i in range(1,count+1):
url=self.index_url.format(keyword,i)
self.q1.put(url)
#核心函数:解析一级url,获取二级url并放入队列
def parse_indexurl(self):
#死循环用于阻止线程阻塞
while True:
if not self.q1.empty():
url = self.q1.get()
html=self.get_html(url)
jsonlists=json.loads(html)
for i in jsonlists["Data"]["Posts"]:
postsid=i['PostId']
#格式化二级url
url=self.second_url.format(postsid)
#将二级url放入二级url的队列
self.q2.put(url)
else:
break
# 核心函数:解析二级url,获取职位信息
def parse_secondurl(self):
# 死循环用于阻止线程阻塞
while True:
if not self.q2.empty():
url=self.q2.get()
html=self.get_html(url)
jsonlist=json.loads(html)
items={}
# 封装数据
items["RecruitPostName"] = jsonlist['Data']['RecruitPostName']
items["LocationName"] = jsonlist['Data']['LocationName']
items["Responsibility"] = jsonlist['Data']['Responsibility']
items['LastUpdateTime'] = jsonlist['Data']['LastUpdateTime']
print(items)
#加锁(防止线程争抢资源),找到一个工作总数加1
self.lock.acquire()
self.jobcount+=1
self.lock.release()
else:
break
# 执行函数:程序执行的入口
def run(self):
#获取职位关键词
keyword=input("请输入搜索的职位:")
keyword=parse.quote(keyword)
#调用入队函数,把一级页面都放入队列
self.url_inQueue(keyword)
t1_lists=[]
t2_lists=[]
# 开启1个线程用于抓取一级页面的url
for i in range(1):
t=Thread(target=self.parse_indexurl())
t1_lists.append(t)
t.start()
# 开启大于1个线程用于抓取二级页面的url,缩短抓取时间
for i in range(2):
t=Thread(target=self.parse_secondurl())
t2_lists.append(t)
t.start()
# 阻塞线程
for t in t1_lists:
t.join()
for t in t2_lists:
t.join()
if __name__=="__main__":
start_time=time.time()
spider=TecentJobs_spider()
spider.run()
end_time=time.time()
print("耗时:%.2f" % (end_time-start_time))
print("职位数量:",spider.jobcount)
注意:
from UserAgent import get_UserAgent : 这是我的一个反爬策略(通过交替User-Agent),代码如下
import random
agentPools=[
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0"
]
def get_UserAgent():
return agentPools[random.randint(0,2)]
执行效果: