最近正是 2023 届秋招的时候. 作为计算数学专业的研究生, 我在找工作时发现选择是如此之少, 每天要浏览大量的招聘网站和岗位信息. 昨天打开中国电信的招聘网站, 发现它有大量的分公司, 每个分公司又有大量的子分公司, 每个子分公司又有大量的岗位. 一个个点来, 却鲜少找到其中的数学. 心生悲哀, 所以想写一个爬虫, 让我们来找找电信中的数学.
访问每个分公司
首先我们点进中国电信 2023 校招的网站.
随便点击一个分公司, 可以看到地址栏出现了两个 id 的数值.
根据以往的经验, 这个数值的变化应该就会导向不同的分公司. 测试一下, 果然如此. coid
的后三位变化对应不同的分公司.
那么访问每个分公司的代码就已经写好了:
url = "http://campus.51job.com/chinatelecom2023/jobdetail.html?ctmid=6271848&type=1&coid=7324" + str(companyIdx)
让 companyIdx
跑起来, 就可以依次访问每个分公司的招聘网址了.
访问每个子分公司
下面我们再看一看各个分公司的子分公司.
失望地发现, 子分公司改变不会带来网址的变化, 也就是说这个网页是动态加载的. 一个直观且方便的方法是使用 selenium 模拟点击, 获得对应的页面. 为此, 我们打开浏览器的开发者模式, 检查一下我们应该点击网页的什么模块.
按 F12 打开开发者模式, 在 elements(元素) 标签按 ctrl+F 搜索延边分公司, 定位到它的类为 divItem
:
那么接下来的代码就很清楚了:
try:
browser = webdriver.ChromiumEdge(options=edge_options)
browser.get(url)
subCompanies = browser.find_elements("class name", "divItem")
for subComp in subCompanies:
# 点击进入当前子分公司, 点开所有职位
subComp.click()
time.sleep(2)
pass # 这里展开每个岗位的信息
except Exception:
return None
展开每个岗位信息
再看一下每个岗位的信息, 发现它们和子分公司一样是动态的, 那我们也模拟点击一下. 同样, 我们去查看一下应该点击什么元素:
jobList = browser.find_elements("class name", "listName")
for job in jobList:
job.click()
time.sleep(0.3)
现在我们把当前分公司的每个岗位都展开了, 接下来就可以用处理静态网页的方法查找抓取我们想要的信息了:
html = browser.page_source
查找里面的数学
查找网页的元素可以用 BeautifulSoup, 当然也可以直接用正则表达式. 这里我们就用正则表达式写一下吧. 我们先研究一下我们想要获得什么样的信息.
首先回顾我们一开始的目的就是爬取所有招聘专业有数学的岗位, 这就给了一个清晰的限制:
- 要匹配一个描述岗位信息的 element;
- 岗位信息里要有 “数学”, “数学类”, “专业” 这样的限定词.
研究一下网页源码:
可以看见展开后的描述岗位信息的源码放在 <div class="listBottom actList">
这块元素里了.
接下来我们再把岗位名字和单位名字匹配出来, 这就是我们想要的结果了.
pattern = re.compile('<div class="listBottom actList">.*?岗位\ *(.*?)\ *.*?<br.*?单位\ *(.*?)\ *.*?<br.*?数学.*?</div>', re.S)
jobs = re.findall(pattern, html)
记录带数学的岗位信息
然后打开一个文件把获取到的信息写进去即可.
def writeToFile(jobInfo):
"""写入岗位信息"""
print("writing now \n")
with open('./containMathJobs.txt', 'a', encoding='UTF-8') as f:
f.write(json.dumps(jobInfo, ensure_ascii=False) + '\n')
f.close()
return
总结
更进一步, 应该用多线程多进程来加速一下. 但这里就不搞了, 只爬几个省份的话也用不了多久.
总结一下,
- 先获取访问各分公司网址的规则,
- 再模拟点击展开每个分公司的各个子分公司的各个岗位,
- 在此时的网页源码中匹配想要的信息
- 把获取到的信息保存到文件中
代码
import re
from selenium import webdriver
from selenium.webdriver.edge.options import Options
import time
import json
def getSubCompanies(url):
"""查找当前分公司的所有子分公司, 生成展开职位的列表"""
try:
browser = webdriver.ChromiumEdge(options=edge_options)
browser.get(url)
subCompanies = browser.find_elements("class name", "divItem")
for subComp in subCompanies:
# 点击进入当前子分公司, 点开所有职位
subComp.click()
time.sleep(2)
jobList = browser.find_elements("class name", "listName")
for job in jobList:
job.click()
time.sleep(0.3)
html = browser.page_source
yield html
except Exception:
return None
def findMathIn(html):
"""找到想要的岗位信息"""
pattern = re.compile('<div class="listBottom actList">.*?岗位\ *(.*?)\ *.*?<br.*?单位\ *(.*?)\ *.*?<br.*?数学.*?</div>', re.S)
jobs = re.findall(pattern, html)
for jobInfo in jobs:
yield {
"招聘岗位":jobInfo[0],
"用人单位":jobInfo[1]
}
def writeToFile(jobInfo):
"""写入岗位信息"""
print("writing now \n")
with open('./containMathJobs.txt', 'a', encoding='UTF-8') as f:
f.write(json.dumps(jobInfo, ensure_ascii=False) + '\n')
f.close()
return
def main(companyIdx):
url = "http://campus.51job.com/chinatelecom2023/jobdetail.html?ctmid=6271848&type=1&coid=7324" + str(companyIdx)
subCompPages = getSubCompanies(url)
for html in subCompPages:
jobsInfos = findMathIn(html)
for job in jobsInfos:
writeToFile(job)
if __name__ == "__main__":
# 设置参数
edge_options = Options()
# 关闭图形界面
edge_options.add_argument("--headless")
edge_options.add_argument("--disable-gpu")
# # 反反爬青春版
# edge_options.add_experimental_option('excludeSwitches', ['enable-automation'])
# edge_options.add_argument('--disable-blink-features=AutomationControlled')
for companyIdx in range(396, 497): # 没有必要. 还是先有个想去的城市再选岗位吧?
main(companyIdx)