一、网站分析
1、确定网站的类型(动态/静态)
- 查看以下页面的网页源代码,发现里面全是一些JS文件的路径,而并无实际的页面数据,则判定当前网站为【动态】页面。
2、确定数据传递方式
- 对于动态网页数据都是AJAX技术使用XHR对象实现的,数据以JSON格式的数据传递,本例以 Chrome浏览器的控制台 来进行数据的抓包,则右击【检查】--> 直接切换至【Network】 --> 选项卡中的【XHR】,来查看哪些请求中是包含了关于职业的相关信息数据。
3、确定请求URL与请求信息
- 查看当前包含数据的URL地址及请求方式:
- ① 表示包含请求数据的URL;② 表示请求的方法。
- 查看请求头信息【headers:用于伪装当前请求是否为爬虫】:
【参数解读】通过多次分析(详细分析不再一一赘述,请按照分析结果自行查询):
- timestamp:1605619289180【明显是:13位的时间戳,Python实现方法:int(time.time()*1000)】
- countryId:1 【表示左侧 国家/地区 编号】
- cityId:1 【表示左侧 城市 编号】
- bgIds:29294 【表示左侧 事业 编号 】
- productId: 【这个多次选择之后,发现没有什么编号,一直是为空值】
- categoryId: 40001001,40001002,40001003,40001004,40001005,40001006 【表示左侧 职业类别 编号】
- parentCategoryId: 【表示父分类的编号,如技术、产品、设计等】
- attrId: 1 【表示招聘类型,社招之类,可以用于控制,灵活搜索查询类别】
- keyword: python 【表示查询关键字,可以用于控制,灵活搜索查询职位】
- pageIndex: 1 【表示当前的页码,可以控制页码】
- pageSize: 10 【表示每页数量,每页10条数据(固定不变)】
- language: zh-cn 【语言,表示当前的使用的语言(固定不变)】
- area:cn 【貌似是表示中国(固定不变)】
【总结】一级页码请求的URL地址:‘https://careers.tencent.com/tencentcareer/api/post/Query?timestamp={}&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword={}&pageIndex={}&pageSize=10&language=zh-cn&area=cn',只需传入时间戳、查询职位、查询的页码 3个参数(其他参数自定义,本文章不做过多的构建阐述)。
4、分析职位的数量
- 通过设置查询的关键字,可以得到查询的结果数据;如果需要爬取多页,则需要分析出当前页面中共有多少条数据结果?也就可以循环对应的次数实现
- 例如:以上搜索了【爬虫】相关的职位,腾讯招聘为我们提供的招聘数据只有 7条,而我们需要关注当前招聘的总数在 XHR 中的一个请求中的,所以我们要实现的是找到对应的请求,获取总条数值,方便我们在后面进行循环,实现多页的爬取。
5、分析二级页面【每条职位详细数据】
(1)分析数据来源
- 通过分析,在 控制台的【XHR】选中的请求中,查看到了对应的数据。
(2)分析对应的请求地址及相关参数
- ① 表示请求的URL地址;② 表示请求的方式。
- 请求头信息(header):用于构建请求详情页的头信息。
- 请求参数:
- timestamp: 1605663485617 【明显是:13位的时间戳,Python实现方法:int(time.time()*1000)】
- postId: 1257927461836955648 【表示当前职位的 ID号】
- language: zh-cn 【语言,表示当前的使用的语言(固定不变)】
【总结】如果要获取请求详情页则需要从一级页面解析出 每条职位信息的ID号 ,URL地址为:'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp={}&postId={}&language=zh-cn',需要传入 时间戳 及 职位ID号 。
二、爬取一级页面数据
1、获取一级页面数据(职位ID号)
- 找到对应职位信息的URL地址,粘贴到浏览器的一个新的选项卡中,粘贴正常的响应JOSN数据,可以使用JOSN解析【https://www.json.cn/】格式化输出,更加清晰查看到当前的数据结构,能够好的实现数据的提取。
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:blning
# File:TencentJob.py
import time
from urllib import parse
from fake_useragent import UserAgent
import requests
class TencentJobSpier:
def __init__(self):
# timestamp:时间戳;keyword:查询参数;pageIndex:查询页面
self.url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp={}&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword={}&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
# 构建请求头(使用 fake_useragent 构建随机的请求头)
self.header = {'User-Agent': UserAgent().random}
# 存储所有的 职位ID号
self.jobID_list = []
def get_html(self, url):
'''
根据不同的请求URL和请求头信息获取对应的页面JSON格式数据
:param url: 发送请求的URL地址
:param headers: 一级页面或二级页面的请求头信息
:param params: 一级页面或二级页面的请求参数
:return: 请求成功返回HTML页面的JSON格式数据,否则返回None
'''
try:
response = requests.get(url=url, headers=self.header, timeout=5)
if response.status_code == 200:
return response.json()
else:
return None
except Exception as e:
print(f'请求异常:{e}')
return None
def parse_one_html(self, kword):
'''
解析获取职位数据的ID号,并存储至self.jobID_list列表中
:param kword: 查询的职位名称
:return: None
'''
timestamp = int(time.time() * 1000)
keyword = parse.quote(kword)
url = self.url.format(timestamp, keyword, 1)
one_html_data = self.get_html(url)
if one_html_data:
datas = one_html_data["Data"]["Posts"]
for data in datas:
self.jobID_list.append(data["PostId"])
# 主函数
def main(self):
kword = input('请输入要查询的职位:')
self.parse_one_html(kword)
print(self.jobID_list)
if __name__ == '__main__':
tencentJob = TencentJobSpier()
tencentJob.main()
三、解析二级页码数据
- 同样将对应URL地址中的数据通过使用JOSN解析【https://www.json.cn/】格式化输出。
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:blning
# File:TencentJob.py
import random
import time
from urllib import parse
from fake_useragent import UserAgent
import requests
class TencentJobSpier:
def __init__(self):
# 二级页面的URL地址
self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp={}&postId={}&language=zh-cn'
# 构建请求头(使用 fake_useragent 构建随机的请求头)
self.header = {'User-Agent': UserAgent().random}
# 存储所有的 职位ID号
self.jobID_list = []
# 存储所有的 职位数据
self.jobData_list = []
def parse_two_html(self, PostID):
'''
解析二级页面数据,存储至self.jobData_list列表中
:param PostID: 职位的ID号
:return: None
'''
timestamp = int(time.time() * 1000)
url = self.two_url.format(timestamp, PostID)
two_html_data = self.get_html(url)
if two_html_data:
job_name = two_html_data["Data"]["RecruitPostName"]
job_location = two_html_data["Data"]["LocationName"]
job_category = two_html_data["Data"]["CategoryName"]
job_responsibility = two_html_data["Data"]["Responsibility"]
job_requirement = two_html_data["Data"]["Requirement"]
job_lastUpdateTime = two_html_data["Data"]["LastUpdateTime"]
self.jobData_list.append((job_name, job_location, job_category, job_responsibility, job_requirement, job_lastUpdateTime))
# 主函数
def main(self):
kword = input('请输入要查询的职位:')
self.parse_one_html(kword)
for postID in self.jobID_list:
print(f'正在请求的职位ID为:{postID}')
self.parse_two_html(postID)
time.sleep(random.uniform(1,3))
print(self.jobData_list)
if __name__ == '__main__':
tencentJob = TencentJobSpier()
tencentJob.main()
四、多页的爬取
- 获取输入职位总数为多少,计算出爬取的页数,及:page = totalNum // 10 + 1,总得数量在首页就有,则可以直接获取。
class TencentJobSpier:
def parse_one_html(self, kword, page, id_list):
'''
解析获取职位数据的ID号,并存储至self.jobID_list列表中
:param kword: 查询的职位名称
:param page: 请求的页码数
:param id_list: 存储每页所有的职位ID列表
:return: None
'''
timestamp = int(time.time() * 1000)
keyword = parse.quote(kword)
url = self.url.format(timestamp, keyword, page)
one_html_data = self.get_html(url)
if one_html_data:
datas = one_html_data["Data"]["Posts"]
for data in datas:
id_list.append(data["PostId"])
def get_total(self, kword):
'''
获取首页的职位总数并返回
:param kword: 查询中的职位关键字
:return: 职位的总招聘数量
'''
timestamp = int(time.time() * 1000)
keyword = parse.quote(kword)
url = self.url.format(timestamp, keyword, 1)
html = self.get_html(url)
return html["Data"]["Count"]
# 主函数
def main(self):
kword = input('请输入要查询的职位:')
total = self.get_total(kword)
page = total // 10 + 1
for i in range(1, total+1):
print(f'正在爬取第 {i} 页数据....')
id_list = [] # 由于每次请求需要存储本页的职位ID做为爬取对象,则每次循环则需要清空
self.parse_one_html(kword, i, id_list)
for postID in id_list:
print(f'正在请求的职位ID为:{postID}')
self.parse_two_html(postID)
time.sleep(random.uniform(1,3)) # 每个详细页休眠
time.sleep(random.uniform(1, 3)) # 每次循环请求下一页休眠
print(self.jobData_list, len(self.jobData_list))
if __name__ == '__main__':
tencentJob = TencentJobSpier()
tencentJob.main()
五、最终代码
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:blning
# File:TencentJob.py
import random
import time
from urllib import parse
from fake_useragent import UserAgent
import requests
class TencentJobSpier:
def __init__(self):
# timestamp:时间戳;keyword:查询参数;pageIndex:查询页面
self.url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp={}&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword={}&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
# 二级页面的URL地址
self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp={}&postId={}&language=zh-cn'
# 构建请求头(使用 fake_useragent 构建随机的请求头)
self.header = {'User-Agent': UserAgent().random}
# 存储所有的 职位数据
self.jobData_list = []
def get_html(self, url):
'''
根据不同的请求URL和请求头信息获取对应的页面JSON格式数据
:param url: 发送请求的URL地址
:param headers: 一级页面或二级页面的请求头信息
:param params: 一级页面或二级页面的请求参数
:return: 请求成功返回HTML页面的JSON格式数据,否则返回None
'''
try:
response = requests.get(url=url, headers=self.header, timeout=5)
if response.status_code == 200:
return response.json()
else:
return None
except Exception as e:
print(f'请求异常:{e}')
return None
def parse_one_html(self, kword, page, id_list):
'''
解析获取职位数据的ID号,并存储至self.jobID_list列表中
:param kword: 查询的职位名称
:param page: 请求的页码数
:param id_list: 存储每页所有的职位ID列表
:return: None
'''
timestamp = int(time.time() * 1000)
keyword = parse.quote(kword)
url = self.url.format(timestamp, keyword, page)
one_html_data = self.get_html(url)
if one_html_data:
datas = one_html_data["Data"]["Posts"]
for data in datas:
id_list.append(data["PostId"])
def parse_two_html(self, PostID):
'''
解析二级页面数据,存储至self.jobData_list列表中
:param PostID: 职位的ID号
:return: None
'''
timestamp = int(time.time() * 1000)
url = self.two_url.format(timestamp, PostID)
two_html_data = self.get_html(url)
if two_html_data:
job_name = two_html_data["Data"]["RecruitPostName"]
job_location = two_html_data["Data"]["LocationName"]
job_category = two_html_data["Data"]["CategoryName"]
job_responsibility = two_html_data["Data"]["Responsibility"]
job_requirement = two_html_data["Data"]["Requirement"]
job_lastUpdateTime = two_html_data["Data"]["LastUpdateTime"]
self.jobData_list.append((job_name, job_location, job_category, job_responsibility, job_requirement, job_lastUpdateTime))
def get_total(self, kword):
'''
获取首页的职位总数并返回
:param kword: 查询中的职位关键字
:return: 职位的总招聘数量
'''
timestamp = int(time.time() * 1000)
keyword = parse.quote(kword)
url = self.url.format(timestamp, keyword, 1)
html = self.get_html(url)
return html["Data"]["Count"]
# 主函数
def main(self):
kword = input('请输入要查询的职位:')
total = self.get_total(kword)
page = total // 10 + 1
for i in range(1, total+1):
print(f'正在爬取第 {i} 页数据....')
id_list = [] # 由于每次请求需要存储本页的职位ID做为爬取对象,则每次循环则需要清空
self.parse_one_html(kword, i, id_list)
for postID in id_list:
print(f'正在请求的职位ID为:{postID}')
self.parse_two_html(postID)
time.sleep(random.uniform(1,3)) # 每个详细页休眠
time.sleep(random.uniform(1, 3)) # 每次循环请求下一页休眠
print(self.jobData_list, len(self.jobData_list))
if __name__ == '__main__':
tencentJob = TencentJobSpier()
tencentJob.main()
【结语】以上整个程序的实现逻辑,还存在很多不全面的地方,欢迎各位大佬指点;如果觉得笔者不易,请给予点赞,给予我记录更多文章的动力!!