Python多線程爬蟲,騰訊招聘網職位爬取程序,Ajax異步數據爬取模板

目錄

騰訊招聘網職位爬取程序

1.需求分析

2.URL分析

3.程序設計思路

4.設置多線程

5.程序代碼 


騰訊招聘網職位爬取程序

1.需求分析

騰訊招聘網首頁URL:https://careers.tencent.com/search.html

首頁與大部分求職網並無太大差別,我們的目的是爬取某個崗位(如運維,設計爬蟲程序時會提示輸入工作名稱)的所有工作崗位信息。

這些信息包括:崗位名稱、發起時間、工作地點,工作職責、工作要求。

 

2.URL分析

1)分析首頁URL

首先看到騰訊招聘網的首頁:https://careers.tencent.com/search.html,查看源碼:

啥也沒有,說明所有的招聘信息都是JS嵌進去的,或者說是異步(AJax)獲取的。

進入控制檯,點擊Network,點擊XHR,找到兩個文件,我們在Query開頭的文件中獲取到了崗位信息。

 

 URL一長串,提取出來之後是:https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1582098990287&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn

下面來分析這一段URL。

 在地址欄中輸入這一串URL,我的格式之所以這麼清晰是以爲裝了一個叫JSONView的插件(谷歌瀏覽器插件,安裝教程:https://blog.csdn.net/ck784101777/article/details/104291634

下面來分析這一段URL,此URL有很多參數,我們的目的是儘量的縮短這個URL的長度(不縮短也可以),進過縮短,發現必須保留的參數有timestampkeyword、pageIndex、pageSize

 

timestamp:好理解,時間樁,進過我的測試這個時間樁寫什麼都無所謂,但是必須有,所以我們下面讓他爲1就可以了。

keyword:搜索的職位名稱,爲空時代表搜索全部職位,當搜索java工程師時keyword=java工程師

pageIndex:代表頁數,一個URL下只有10條職位信息,頁數從1開始,我們接下來也要計算某職位的頁數,只有這樣才能將數據抓全

pageSize:固定值,職位數量,這裏是10

經過縮短,這條URL變爲(其中keyword和pageIndex是變化的):

https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1&keyword=&pageIndex=1&pageSize=10

 

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的值。

https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1&keyword=&pageIndex=1&pageSize=10

#功能函數:將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)]

 

執行效果:

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章