上次从智联爬取了些岗位信息,听说前程无忧的用人企业答复率更高,所以今天再爬一下前程无忧。
封装好的程序:
个人网站下载
(网址:http://123.206.72.236:8080/)
封装加壳的程序会让杀毒软件报毒
使用说明:
城市代码获取方法: 在前程无忧选择需要爬取的城市搜索一次,最多5个城市。
没做错误处理,噼啪报错是正常的不用担心,报错的行会没有职位信息。
前戏
正式开搞之前依旧是要先揉捏一下,有了第一次的经验,这次前戏更深入一些:
1、动态还是静态?
2、cookie?
3、url,headers,params?
首先是第一个问题,老手法F12结合网页源代码找div。
可以看出来是静态网页。所以决定直接get然后分析源代码取数据。
至此第一步结束。
第二步关于cookie,测试发现不登录也可以正常取到数据,所以不考虑cooike的问题
第三步分析url,headers,params。需要对比不同城市和搜索条件以及相同城市搜索条件不同分页这三个页面。
先搞url。对比发现红线处标记的位置分别对应城市代码、搜索条件、页数。由于是自己用的脚本,暂时不考虑可拓展性,所以其他组成按常量对待。城市代码可以多输真是太棒了,多个城市一次搞定。
搞headers时用了Chrome的无痕模式模拟首次访问,不用删cookie。
都是些常规信息,在这个基础上加上个Referer: https://www.51job.com/,防止意外。需要注意的是Sec-Fetch-Site需要修改为Sec-Fetch-Site: same-site。
对比三个页面的params,发现参数是静态的。
至此前戏结束,太顺利了心里感觉毛毛的。。。不管了先开搞。
开搞
目标:爬取青岛、上海、济宁三地的python相关职位和其内部的职位信息内容。
由于城市可以多选,所以直接爬取一个url就行,然后获取到分页信息后并发爬取所有分页的职位。获取所有职位后再并发爬取职位界面的url,爬内部的职位信息。
因为正则表达式不熟练,所以本次用正则表达式提取数据而不用BeautifulSoup。
代码如下:
# -*- coding: utf-8 -*-
'''
前程无忧爬取,静态网页
爬取内容:
青岛python
上海python
济宁python
爬取到所有的职位后,进入每条招聘信息内爬取职位信息。
'''
import copy
import json
import math
import asyncio
import aiohttp
import requests
import re
import math
import multiprocessing
class Spider():
def __init__(self, city='' ,kw='' ,url=None, p=1, index=0):
self.p = p
self.index = index
if city:
self.city = city
else:
self.city = '120300%252C020000%252C120900'
if bool(kw):
self.kw = kw
else:
self.kw = 'python'
self.flag = True
if url:
self.url = url
self.flag = False
else:
self.url = r'https://search.51job.com/list/{},000000,0000,00,9,99,{},2,{}.html'.format(self.city, self.kw, self.p)
self.headers = {
'Accept': r'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Accept-Encoding': r'gzip, deflate, br',
'Accept-Language': r'zh-CN,zh;q=0.9',
'Connection': r'keep-alive',
'Host': r'search.51job.com',
'Referer': r'https://www.51job.com/',
'Sec-Fetch-Mode': r'navigate',
'Sec-Fetch-Site': r'same-site',
'Sec-Fetch-User': r'?1',
'Upgrade-Insecure-Requests': r'1',
'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
}
self.params = {
'lang': r'c',
'stype': r'',
'postchannel': r'0000',
'workyear': r'99',
'cotype': r'99',
'degreefrom': r'99',
'jobterm': r'99',
'companysize': r'99',
'providesalary': r'99',
'lonlat': r'0,0',
'radius': r'-1',
'ord_field': r'0',
'confirmdate': r'9',
'fromType': r'',
'dibiaoid': r'0',
'address': r'',
'line': r'',
'specialarea': r'00',
'from': r'',
'welfare': r'',
}
async def asyncget(self):
async with aiohttp.ClientSession() as session:
await asyncio.sleep(0.25)
async with session.get(url=self.url, params=self.params, headers=self.headers) as resp:
#反反爬并等待ssl底层机制,防止ssl验证失败。
await asyncio.sleep(0.25)
if self.p != 1 : print('已完成第 {} 页'.format(self.p))
try:
response = await resp.text(encoding = 'gbk')
except Exception:
dp.cleaning('', self.flag, self.index)
else:
dp.cleaning(response, self.flag, self.index)
return
def getpage(self):
response = requests.get(url=self.url,params=self.params,headers=self.headers)
response.encoding = 'gbk'
p = int(re.findall(r'<span class="td">\D+(\d+)\D+</span>',response.text)[0])
dp.cleaning(response.text, self.flag, self.index)
return p
class DataProcessor():
def __init__(self):
pass
def cleaning(self, text, flag, index):
if flag:
list_ = re.findall(r'<div class="el">((?:.|\n)*?)</div>',text)
for l in list_:
d = list(re.findall(r'.*title="{a}" href="{a}"(?:.|\n)*title="{a}"(?:.|\n)*<span class="t3">{a}</span>(?:.|\n)*<span class="t4">{a}</span>(?:.|\n)*<span class="t5">{a}</span>'.format(a = '(.*?)'),l)[0])
u = d.pop(1)
d.append(u)
data_list.append(d)
else:
try:
message = re.findall(r'<div class="bmsg job_msg inbox">((?:.|\n)*?)<div class="',text)[0]
except Exception:
message = ''
message = re.sub(r'\s|<.+?>', '', message).replace('\'','"').replace(' ',' ')
try:
company = re.findall(r'<div class="tmsg inbox">((?:.|\n)*?)</div>',text)[0]
company = re.sub(r'\s|<.+?>', '', company).replace('\'','"').replace(' ',' ')
except Exception:
company = ''
data_list[index].append(message)
data_list[index].append(company)
def output(self):
tittle = '职位\t企业\t工作地点\t薪资\t发布日期\t网址\t用人要求\t公司简介\n'
with open('data.txt','a',encoding='utf-8') as f:
f.write(tittle)
for l in finall:
d = '\t'.join(l)+'\n'
f.write(d)
print('实际爬取 {} 行'.format(len(finall)))
def getmessage(listsplit, q):
global dp, data_list
data_list = listsplit
dp = DataProcessor()
loop = asyncio.get_event_loop()
spider_list = []
num = 0
for i in data_list:
spider_list.append(Spider(url=i[-1], index=num))
num += 1
tasks = [asyncio.ensure_future(i.asyncget()) for i in spider_list]
loop.run_until_complete(asyncio.wait(tasks))
q.put(data_list)
def startprocess():
global finall
num = 1
times = math.ceil(len(data_list) / 400)
print('共 {} 次爬取'.format(times))
dl = [data_list[400 * t : 400 * (t + 1)] for t in range(times)]
q = multiprocessing.Queue()
for d in dl:
p = multiprocessing.Process(target=getmessage, args=(d, q))
p.start()
finall += q.get()
p.join()
print('已完成 {} 次爬取,共 {} 次'.format(num, times))
num += 1
def run():
global dp, data_list, finall
print('使用说明请访问本人csdn文章:\n' + r'https://blog.csdn.net/weixin_43305880/article/details/102485450')
print('\n!!注意!!城市范围尽量小,爬取数量超过500页有可能会报错')
city = input('\n请输入城市参数(默认济宁上海青岛): ')
kw = input('\n请输入关键字(默认python): ')
data_list = []
finall = []
dp = DataProcessor()
spider = Spider(city=city, kw=kw)
spider_list = []
p = spider.getpage()
for i in range(2,p+1):
spider_list.append(Spider(p=i))
print('一共 {} 页'.format(p))
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(i.asyncget()) for i in spider_list]
loop.run_until_complete(asyncio.wait(tasks))
print('一共 {} 行'.format(len(data_list)))
startprocess()
dp.output()
if __name__ == '__main__':
run()
事后
这次代码不是最优解,权当做练习携程和用一用多进程。
1)aiohttp底层使用的是selector机制,单进程支持的文件描述符linux最多1024windows默认是509。这里强行另开了一个进程进行爬取。
2)ssl验证需要越0.25s的时间。
3)招聘信息界面的职位信息不好用re匹配,考虑使用bs改写代码。
需要掌握的新姿势
1、多并发问题怎么处理?
2、bs实现职位信息的截取。
3、用aiohttp搭个小网站服务器。