多线程爬取招聘网站,帮你年后找工作

本爬虫仅供学习交流,请勿将爬取数据进行非法使用。

功能简介

本爬虫可爬取各大互联网行业常用招聘网站(目前包括 拉勾网、BOSS直聘、前程无忧、猎聘网,更多可自定义),采集职位主要信息输出到 csv 文件;
爬虫和文件写入独立两个进程(其实没必要,为了练习),进程A对每个网站的爬虫启动多线程,每个爬虫以生成器方式迭代返回数据,通过队列传输给进程B进行写入。

运行环境

  • Python 3
  • requests
  • lxml

代码简介

首先定义了爬虫的元类和基类,元类用来自动注册爬虫类到列表,进程只要遍历这个列表就能获得所有爬虫类了

1
2
3
4
5
6
7
8
9
10
# job_spider/spider.py

class SpiderMeta(type):
"""爬虫类的元类,注册子类到列表,爬虫类指定此元类才能加入进程"""

spiders = []

def __new__(mcs, name, bases, attrs):
mcs.spiders.append(type.__new__(mcs, name, bases, attrs))
return type.__new__(mcs, name, bases, attrs)

因为发现这些招聘网站对访问限制很高,所以在基类里实现了一个可以保持请求间隔和带默认 headers 的请求方法,每次请求都会计算与上次请求的间隔,间隔不够就等待,这样就不用每次 requests 后边都跟着 time.sleep 了;另外还加入了随机系数来对间隔进行浮动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# job_spider/spider.py

class BaseSpider(object):
"""爬虫类的基类,提供需要的属性和方法"""

# 省略部分代码

def request(self, method='get', url=None, encoding=None, **kwargs):
"""
根据爬虫类重新封装的`requests`,可保持请求间隔,并带有默认头部
:param method: 请求方法,`get`或`post`等
:param url: 请求链接
:param encoding: 指定对返回对象进行编码
:param kwargs: 其他`requests`自带的参数
:return: Response 对象
"""
# 没有指定头部则使用默认头部
if not kwargs.get('headers'):
kwargs['headers'] = self.headers
# 随机生成系数对间隔产生变化
rand_multi = random.uniform(0.8, 1.2)
# 距离上次请求的间隔
interval = time.time()-self._time_recode
# 如间隔小于最短间隔,则进行等待
if interval < self.request_sleep:
time.sleep((self.request_sleep-interval)*rand_multi)
resp = getattr(requests, method)(url, **kwargs)
# 请求完重新记录时间戳
self._time_recode = time.time()
if encoding:
resp.encoding = encoding
return resp

爬虫具体代码就不说了,主要是实现一个 crawl 方法,用 yield 返回数据。

下面是进程的代码,将元类列表的爬虫类分别传入 iter_spider 中,并启动一个线程即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# job_spider/process.py

class SpiderProcess(Process):
"""爬虫进程"""

# 省略部分代码

def iter_spider(self, spider):
"""对爬虫类的`crawl`方法进行迭代,数据送入队列传给另一进程"""
setattr(spider, 'job', self.job)
setattr(spider, 'city', self.city)
generator = spider.crawl()
if generator:
for result in spider.crawl():
self.data_queue.put(result)
self.logger.debug('%s %s %50s...(省略)' % (result.get('title'), result.get('url'),
result.get('description')))
self.logger.info('%s 爬虫已结束' % spider.__class__.__name__)

def run(self):
"""对每个爬虫类启动单独线程"""
self.set_logging()
spiders = [cls() for cls in SpiderMeta.spiders]
spider_count = len(spiders)
threads = []
for i in range(spider_count):
t = Thread(target=self.iter_spider, args=(spiders[i], ))
t.setDaemon(True)
t.start()
threads.append(t)
while True:
time.sleep(1)

写数据进程比较简单,主要是按当前时间创建 csv 然后从队列里获取数据写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# job_spider/process.py

class WriterProcess(Process):
"""写数据进程"""

def __init__(self, data_queue):
Process.__init__(self)
self.data_queue = data_queue

def run(self):
"""以当前时间创建 csv 文件,并从队列中获取数据写入"""
csv_name = datetime.now().strftime('%Y-%m-%d %H-%M-%S') + '.csv'
with open(csv_name, 'w', encoding='utf_8_sig', newline='') as f:
writer = csv.writer(f)
writer.writerow(['标题', '公司', '薪水', '经验',
'学历', '链接', '描述'])
while True:
try:
result = self.data_queue.get(timeout=90)
if result:
row = [
result.get('title'), result.get('company'),
result.get('salary'), result.get('experience'),
result.get('education'), result.get('url'),
result.get('description')
]
writer.writerow(row)
except queue.Empty:
f.close()

运行方式

方法一:使用命令行参数
$ python3 run.py -j 后端 -c 北京

方法二:直接运行,根据提示输入参数
$ python3 run.py
请输入职业:后端
请输入城市:北京

$ python3 run.py -j pyhton -c 上海,结束后会生成 Excel 表格:

方便筛选和统计

代码仓库

https://github.com/zkqiang/Job-Spider