threading模块
threading是多线程的一个模块。所谓多线程,就是实现多个线程并发执行的技术。
使用多线程能帮助我们提升整体处理性能,也就是让我们的爬虫更快。
但是python有一个不同,python具有GIL锁,也就是全局解释器锁,也就是在同一时间只能有一个线程执行,GIL锁就像通行证一样,只有一张,所以python的多线程指的是线程间快速切换来增加速度。
虽说有GIL锁,但是依旧能提高不少效率,如果于我们之后要学习的redis进行结合,效率会更上一步,废话不多说,开始程序解说。
程序解析
首先给出我们的代码和解析(完整代码可查看GitHub):
#导入库
import requests
from bs4 import BeautifulSoup
import time
import re
import class_connect
import threading
#把所有要请求的网址放入link_list列表中
link_list=[]
for i in range(1,208):
url='http://campus.chinahr.com/qz/P'+str(i)+'/?job_type=10&'
link_list.append(url)
#连接数据库的类的实例化
a = class_connect.spider()
collection = a.connect_to_mongodb()
cur, conn = a.connect_to_mysql()
#重写Thread方法并继承threading.Thread父类
class myThread(threading.Thread):
def __init__(self,name,link_range):
#使用Thread的__init__(self)
threading.Thread.__init__(self)
#定义线程名称和每个线程爬取的网站数
self.name=name
self.link_range=link_range
def run(self):
#crawler为主函数
print('Starting '+self.name)
crawler(self.name,self.link_range)
print('Exiting '+self.name)
# 网站请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#记录开始时间
scrapy_time = time.time()
#主函数开始
def crawler(threadName,link_range):
#因为在函数内,所以需要重新连接MySQL数据库,否则会引发错误
cur, conn = a.connect_to_mysql()
#循环网站列表
for i in range(link_range[0],link_range[1]+1):
#依次抽取网站并请求
link = link_list[i-1]
r = requests.get(link, headers=headers, timeout=20)
#=使用BeautifulSoup解析网页
soup = BeautifulSoup(r.text, "lxml")
#使用soup.find_all找到我们需要的
salary_list = soup.find_all('strong', class_='job-salary') #工资
city_list = soup.find_all('span', class_="job-city Fellip") #城市
top_list = soup.find_all('div', class_="top-area") #名称和公司
job_info = soup.find_all('div', class_='job-info') #城市,学历和人数
type_list = soup.find_all('span', class_='industry-name') #类别
#循环每一条招聘信息
for x in range(len(top_list)):
#使用strip()提取文字信息
salary = salary_list[x].text.strip() #工资
city = city_list[x].text.strip() #城市
top = top_list[x].text.strip() #名称和公司
job_and_company = top.split('\n', 1) #分开名称和公司
job_information = job_info[x].text.strip() #城市,学历和人数
city_to_people = job_information.split('\n') #分开城市,学历和人数
type = type_list[x].text.strip() #类别
#为了mongodb数据库的字典
all = {"job": job_and_company[0],
"company": job_and_company[1],
"salary": salary,
"city": city,
"type": type}
#用for循环分开城市,学历和人数
for each in range(0, 5):
#使用re正则表达式
first = re.compile(r' ') #compile构造去掉空格的正则
time_for_sub = first.sub('', city_to_people[each]) #把空格替换为没有,等于去掉空格
another = re.compile(r'/') #compile构造去掉/的正则
the_final_info = another.sub('', time_for_sub) #把/替换为没有,等于去掉/
#获取背景和人数并插入字典
if each == 3:
all['background'] = the_final_info #背景
back=the_final_info
if each == 4:
all['people'] = the_final_info #人数
peo=the_final_info
#插入MongoDB和MySQL数据库
collection.insert_one(all)
cur.execute(
"INSERT INTO yingcaiwang(job,company,salary,city,type,background,people) VALUES(%s,%s,%s,%s,%s,%s,%s);",
(job_and_company[0], job_and_company[1], salary, city, type, back, peo)) #SQL语句
conn.commit() #提交变动
#每爬取5页休息3秒
if i % 5 == 0:
print(threadName+" : 第%s页爬取完毕,休息三秒" % (i))
print('the %s page is finished,rest for three seconds' % (i))
time.sleep(3)
#每爬取1页休息1秒
else:
print(threadName+" : 第%s页爬取完毕,休息一秒" % (i))
print('the %s page is finished,rest for one second' % (i))
time.sleep(1)
#平均分配网址
thread_list=[]
link_range_list=[(1,40),(41,80),(81,120),(121,160),(161,207)]
#利用for开启5个线程
for i in range(1,6):
thread=myThread('Thread-'+str(i),link_range_list[i-1])
thread.start()
thread_list.append(thread)
#等待线程执行完成
for thread in thread_list:
thread.join()
#输出总时间
scrapy_end = time.time()
scrapy_time_whole = scrapy_end - scrapy_time
print('it takes {}'.format(scrapy_time_whole))
#提交MySQL的变动并关闭
cur.close()
conn.commit()
conn.close()
所有基本解析已在注释中,接下来介绍一下重点
这里我们把上次的主程序放到了一个函数中并且在后面加入threading的使用,接下来讲解一下threading。
重写Thread方法和平均分配网址
这里我们重写了Thread父类的__init__和run(self)来满足我们的需求。
初次以外,我们需要平均分配网址来使每个线程差不多时间结束。
thread.start()
thread.start()也就是启动线程,我们可以看到线程的名字一般长这样:
Thread-1
Thread-2
......
因此我们可以使用for循环开启线程并加入线程列表中。
thread.join()
thread.join()也就是等待线程完成,我们使用for循环来循环我们的线程列表并等待他们完成,只有线程完成才能继续执行下一步的代码。
多线程就是这样了,下次我们会来讲解threading+queue的结合使用,下次见!