明确目的:将多线程爬虫涉及到的技术点回顾一下
首先,是基本流程,多线程爬虫架构图如下
首先,我们需要回顾一下队列和线程:
**
队列
**
用来存url,和 网页的响应内容,给线程提供数据线程数据
class Queue(object):
"""
enqueue(item) 往队列中添加一个item元素
dequeue() 从队列头部删除一个元素
is_empty() 判断一个队列是否为空
size() 返回队列的大小
"""
def __init__(self):
self.queue = []
def put(self, item):
self.queue.append(item)
def get(self):
return self.queue.pop(0)
def isempty(self):
return self.queue == []
def size(self):
return len(self.queue)
当然,这是手写的,(熟悉下方法的作用),这次我们直接从queue模块,导入对列类
这次我们主要用put()和get()方法,其中给get(block=False)队列将引发Empty异常.
from queue import Queue
urlQueue = Queue()
dataQueue = Queue()
lock = threading.Lock()
url = 'https://www.qiushibaike.com/text/page/{}/'
for i in range(1, 15):
urlQueue.put(url.format(i))
线程
线程操作系统能够进行运算调度的最小单位,进程中的实际运作单位
适用范围
1.服务器中的文件管理或通信控制
2.前后台处理
3.异步处理
- 爬取,解析
这里我们要异步爬取每个url对应的页面,并解析爬取到的数据:异步:访问资源时在空闲等待时同时访问其他资源
区别同步和异步
一个进程启动的多个不相干线程,它们相互之间关系为异步。
同步必须执行到底之后才能执行其他操作,而异步可以任意操作
(任务A执行到一个阶段,需要 任务B的某个结果,A任务会停下来等待)
- 数据持久化,处理高并发
将数据写到数据库,由于线程共享全局变量负责解析的线程会竞争同一资源,导致我们爬取的数据入库时出现异常
因此我们可以加锁实现线程的同步,
from threading import Lock
lock = Lock()
同步解决了线程的安全问题,但是会降低效率,
-
负责爬取:
class SpiderThread(threading.Thread): def __init__(self, s_name, headers, urlQueue, dataQueue): super().__init__() self.name = s_name self.headers = headers self.urlQueue = urlQueue self.dataQueue = dataQueue def run(self): while True: try: url = self.urlQueue.get(block=False) print(self.name + '开始爬取数据') res = requests.get(url=url, headers=self.headers) print(self.name + '完成爬取数据') self.dataQueue.put(res.text) except: break
-
负责解析,数据持久化
class ParseThread(threading.Thread): def __init__(self, p_name, dataQueue, lock, items): super().__init__() self.name = p_name self.dataQueue = dataQueue self.lock = lock self.items = items def run(self): while True: try: html = self.dataQueue.get(block=False) print(self.name + '开始解析数据') self.parse(html) print(self.name + '解析数据完成') except: break def parse(self, html): soup = BeautifulSoup(html, 'lxml') a_list = soup.select('a > h2') for a in a_list: item = {'name': a.string.strip()} with self.lock: self.save(item) def save(self, item): pass
创建线程
def main()
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
}
# 队列
urlQueue = Queue()
dataQueue = Queue()
lock = threading.Lock()
url = 'https://www.qiushibaike.com/text/page/{}/'
for i in range(1, 15):
urlQueue.put(url.format(i))
# 爬取线程
s_name_list = ['甲', '乙', '丙']
s_list = []
for s_name in s_name_list:
s = SpiderThread(s_name, headers, urlQueue, dataQueue)
s.start()
s_list.append(s)
for s in s_list:
s.join()
# 解析线程
p_name_list = ['戊', '己', '庚']
p_list = []
for p_name in p_name_list:
p = ParseThread(p_name, dataQueue, lock, items)
p.start()
p_list.append(p)
for p in p_list:
p.join()