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)]

 

执行效果:

 

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值