python爬虫--多线程的学习--实战爬取糗事百科

1. 为什么要使用多线程

使用单线程的危害:
一旦一个地方卡到不动了,那不就永远等待下去了?
多线程的优点:
为此我们可以使用多线程来处理问题,并且在网络请求中多使用多线程

2. 如何使用

爬虫使用多线程来处理网络请求,使用线程来处理URL队列中的url,然后将url返回的结果保存在另一个队列中,其它线程在读取这个队列中的数据,然后写到文件中去

3. 多线程主要组成部分

3.1 URL队列和结果队列

将将要爬去的url放在一个队列中,这里使用标准库Queue。访问url后的结果保存在结果队列中

初始化一个URL队列

from queue import Queue
urls_queue = Queue()
out_queue = Queue()
3.2 请求线程

使用多个线程,不停的取URL队列中的url,并进行处理:

import threading


class ThreadCrawl(threading.Thread):
    def __init__(self, queue, out_queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.out_queue = out_queue


    def run(self):
        while True:
            item = self.queue.get()

如果队列为空,线程就会被阻塞,直到队列不为空。处理队列中的一条数据后,就需要通知队列已经处理完该条数据

3.3 处理线程

处理结果队列中的数据,并保存到文件中。如果使用多个线程的话,必须要给文件加上锁

lock = threading.Lock()
f = codecs.open('out.txt', 'w', 'utf8')

当线程需要写入文件的时候,可以这样处理:

with lock:
    f.write(something)
4. Queue模块中的常用方法:

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作

本节实验中我所编写的代码:

from threading import Thread
from queue import Queue
from fake_useragent import UserAgent
import requests
# 为了解析,导入这个包
from lxml import etree


# 爬虫类,创建一个爬虫类,让它继承Thread
class CrawInfo(Thread):
    # 定义初始化函数,并将url作为参数传进来,以及我们将要爬取的html页面作为参数传进来
    def __init__(self,url_queue,html_queue):
        Thread.__init__(self)
        self.url_queue = url_queue
        self.html_queue = html_queue

    # 这个run()函数就是让爬虫干具体的活
    def run(self):
        headers = {
            "User-Agent": UserAgent().random
        }
        # 这里写一个循环,为了判断队列中只要不空,就要求爬虫一直干活
        while self.url_queue.empty() == False:
            # 注意这里一定是url_queue.get(),将我们所需的url从队列中取出来
            response = requests.get(self.url_queue.get(), headers=headers)
            # 如果返回代码200,则表示爬取成功,并将爬取的html放到队列中
            if response.status_code == 200:
                self.html_queue.put(response.text)

# 解析类
class ParseInfo(Thread):
    # 定义一个初始化函数,并将html_queue作为参数传进去,便于进行解析
    def __init__(self, html_queue):
        Thread.__init__(self)
        self.html_queue = html_queue

    # 定义一个run函数,告诉这个解析类干什么具体的活
    def run(self):
        while self.html_queue.empty() == False:
            # 将我们的html_queue作为参数传给etree.HTML(),并通过其创建一个对象进行操作
            e = etree.HTML(self.html_queue.get())

            # 通过xpath获取内容,
            span_contents = e.xpath('''//div[@class='content']/span[1]''')

            with open("duanzi9999.txt",'a',encoding='UTF-8') as f:
                for span in span_contents:
                    # 由于该网站自行解析是将每一行都作为一个整体内容解析,此时我们需要使用string()将这几行内容作为一个字符串整体输出
                    # (.)表示从当前节点进行解析,(..)表示从上一节点输出
                    info = span.xpath('string(.)')
                    f.write(info + '\n')




if __name__ == '__main__':

    # 存储URL的容器
    url_queue = Queue()
    # 存储内容的容器,创建这个队列是为了将我们爬取的html存储起来,便于下一步解析使用
    html_queue = Queue()
    base_url = "https://www.qiushibaike.com/text/page/{}/"
    for i in range(1,14):
        new_url = base_url.format(i)
        url_queue.put(new_url)      # 将我们要爬取的url放到队列中,然后将这些url传给爬虫类,让他干活

    # 为了存储我们将要创建的爬虫类线程,只有先存储起来,才能使得爬虫类线程一个一个等待解析类线程干活
    craw_list = []
    # 这里使用for循环是表示使用3次多线程,如果不使用这个循环,则表示,只进行一次url_queue的爬取
    for i in range(0,3):
        # 创建一个爬虫类的线程,url_queue参数是我们传给爬虫告诉它爬取哪里,html_queue参数表示将爬取的页面html放到html队列中
        craw = CrawInfo(url_queue,html_queue)

        # 将创建的爬虫类线程添加到列表中
        craw_list.append(craw)
        # 启动这个爬虫
        craw.start()

    for craw in craw_list:
        # 让爬虫类线程进行等待,等待解析类线程的解析过程,如果没有这一步操作,主线程干完活后就不管解析类线程了
        craw.join()

    parse_list = []
    for i in range(0,3):
        # 创建解析类的线程对象,只用通过对象才能启动线程
        parse = ParseInfo(html_queue)
        parse_list.append(parse)
        # 启动线程
        parse.start()

    for parse in parse_list:
        parse.join()



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值