目錄
騰訊招聘網職位爬取程序
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)]
執行效果: