分析多线程爬取糗百
为了加快爬取网站的效率,我们将使用多线程来爬取任务,我们最简单爬取数据大致思路可以分为三步:
- 1.爬取数据
- 2.解析数据
- 3.保存数据
在使用多线程时,我们就变成了多个线程爬取数据,解析数据。这样我们需要借助队列,并且上锁,来避免线程之间的恶意竞争资源。那么我们可以将思路步骤分为:
- 1.爬取数据写入队列
- 2.队列读取数据
- 3.解析数据写入队列
- 4.队列读取数据
- 5.保存数据
代码解析如下:
导包
导入锁,线程
from threading import Lock,Thread
导入队列
from queue import Queue
请求处理
import requests
解析库
from lxml import etree
导入数据库
import pymongo
采集数据类
class CrawlThread(Thread):
def __init__(self,threadName,pageQueue,dataQueue):
继承父类实例属性的同时 拓展父类属性
super().__init__()
线程名
self.threadName = threadName
页码队列
self.pageQueue = pageQueue
数据队列
self.dataQueue = dataQueue
请求头
self.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",}
重新定义底层run方法
def run(self):
初始一个静态url,为拼接做准备
url = "https://www.qiushibaike.com/text/page/%s/"
while True:
try:
可选参数block,默认值为Ture
如果队列为空 block 为 True 那么进入阻塞状态
如果队列为空 block 为 False 那么会抛出异常
从队列中获取要拼接url的页码
取一个数字,先进先出
page = self.pageQueue.get(block=False)
print("%s开始工作..."%self.threadName)
response = requests.get(url=url%page,headers=self.headers)
content = response.text
获取请求的页面,并写入队列中,已备解析使用
self.dataQueue.put(content)
print("%s工作结束..."%self.threadName)
except:
break
解析数据类
class ParseThread(Thread):
def __init__(self,threadName,dataQueue,lock):
同理调用父类初始化方法
super().__init__()
自定义解析线程名称
self.threadName = threadName
自定义数据队列
self.dataQueue = dataQueue
自定义锁
self.lock = lock
重新写run方法,处理数据
def run(self):
while True:
try:
从采集数据的队列中获取到页面
html = self.dataQueue.get(block=False)
print("%s开始处理数据.........."%self.threadName)
将获取的页面传递给parse函数进行解析
self.parse(html)
print("%s数据处理完毕.........."%self.threadName)
except:
break
解析页面
def parse(self,html):
转成lxml进行匹配
html = etree.HTML(html)
div_list = html.xpath('//div[@id="content-left"]/div')
for div in div_list:
try:
name = div.xpath('.//div[@class="author clearfix"]/a/h2/text()')[0]
joke = div.xpath('.//span[@class="stats-vote"]/i/text()')[0]
con = div.xpath('.//span[@class="stats-comments"]//i/text()')[0]
item = {"作者": name, "搞笑": joke, "评论": con}
上个锁,当一个线程进行解析时,其它将不解析这一条
with 后面有两个必须执行的操作:__enter__ 和 _exit__
不管里面的操作结果如何,都会执行打开、关闭
打开锁、处理内容、释放锁
with self.lock:
讲述得到的数据传到save函数中进行数据库保存
self.save(item)
except:
pass
mongo数据库保存数据
def save(self,item):
建立mongo数据库连接
conn = pymongo.MongoClient("localhost",27017)
创建数据库
db = conn.qiubai
创建表
table = db.qiubai
插入数据
table.insert_one(item)
关闭数据库
conn.close()
主程序准备类工作的材料
def main():
页码队列
pageQueue = Queue()
数据队列
dataQueue = Queue()
准备锁
lock = Lock()
构建循环,准备页码
for i in range(1,14):
将页码写入页码队列
pageQueue.put(i)
**采集线程**
准备三个采集线程名字
crawlList = ["采集1号","采集2号","采集3号"]
存储三个采集线程
ThreadCrawl = []
for var in crawlList:
实例化采集类
c = CrawlThread(var,pageQueue,dataQueue)
#启动采集线程
c.start()
将线程写入列表
ThreadCrawl.append(c)
for var in ThreadCrawl:
采集线程等待,解决程序紊乱
join的作用:join所完成的工作就是线程同步,
即主线程任务结束之后,进入阻塞状态,一直等待其他的
子线程执行结束之后,主线程再终止
var.join()
解析线程
准备三个解析线程名字
parseList = ["解析1号","解析2号","解析3号"]
ThreadParse = []
for var in parseList:
实例化解析线程类
p = ParseThread(var,dataQueue,lock)
启动解析线程
p.start()
存储三个解析线程
ThreadParse.append(p)
for var in ThreadParse:
解析线程等待
var.join()
if __name__ == '__main__':
main()