Python利用创建1000个线程爬取盗版小说网站!并按章节排序!

前言:

上次我写了一篇教程:32个进程爬取小说并存在不同的txt里,有的人说文章在不同txt里不方便,自己也不会合并,那好,今天我们1000个线程(注意上次是进程这次是线程,老是有人搞混)爬取某个小说,并按照章节顺序存在txt里,这个txt我们用主流阅读软件打开都是会自动显示目录的,如下图!

 

上次多进程爬的那个网站已经采取了反爬措施,不行了。所以这次我又找了一个新的盗版网站,反正他是盗版网站,我们也不怕,众所周知,pyhon里如果用多线程访问同一个资源是很容易出错的,并且多线程是无序的,也就是一般来讲,我们用多线程需要用线程锁来决定谁来访问,就算用了线程锁,他的无序也决定了我们无法保证内容是按章节顺序存在txt里的,所以为了解决上面两个问题,我们引入了线程池和PriorityQueue重要性队列,按照事件的重要性来判断先后顺序,这样我们同时请求几百条信息的时候赋值给他们自己的重要性数值,就可以决定谁先写入了,下图是1000个线程爬完1400章所需时间,我们可以看到只花了10秒,这个时间是指整个程序开始,一直到文件写入完毕,是非常快的,我也试了单线程,但是单线程实在太慢了,我没耐心等下去,我算了一下,单线程爬完最少需要2分多钟,我是指最少!


正文

1. 我们先导入所需要的包

import requests, re
from lxml import etree
from queue import PriorityQueue
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import time
12345

这里的重点就是3,4两行了,第三行就是我们的重要性排序的队列,没有他我们不可能在多线程里按顺序写入一个文件内容的,当然,写文件肯定是单线程,但是网页请求是1000个线程,强调一下我是指这个网页请求得来的那1000多个回应是无序的,所以我们单线程写文件时无法按顺序写入,而用到PriorityQueue就可以了,第4行是线程池,我们不能说每一次请求都重新创建一个线程吧,创建线程也是会消耗资源的,所以我们建立一个线程池,保证只有1000个线程来处理你的函数,前一个线程结束了就等待任务到来,而不是关闭再重启,这次你可能体会不到,但是如果别人有10000章的时候,你就能体会到不用线程池时,10000个线程直接把你电脑弄蓝屏的滋味了。

2. 定义两个类(生产者和消费者)

class Spider():
    url = 'http://************/txt/111650/index.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36',
    }
    def get_page_urls(self):
        rsp = requests.get(self.url, headers=self.headers)
        html = etree.HTML(rsp.content)
        titles = html.xpath('//dd/a/text()')[0]
        links = html.xpath('//dd/a/@href')
        links = ['http://**********/txt/111650/'+i for i in links]
        return links
class PageJob():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36',
    }
    def __init__(self, priority, url):
        self.priority = priority
        self.url = url
        self.GetContent()
        return
    def __lt__(self, other):
        return self.priority < other.priority
    def GetContent(self):
        rsp = requests.get(self.url, headers=self.headers)
        print(rsp.status_code)
        if rsp.status_code == 503:
            print(rsp.text)
            sleep(1)
            rsp = requests.get(self.url, headers=self.headers)
        html = etree.HTML(rsp.content)
        title = html.xpath('//h1/text()')[0]
        content = html.xpath('//div[@id="content"]/text()')[:-3]
        while '\r' in content:
            content.remove('\r')
        content = [re.sub('\xa0\xa0\xa0\xa0', '', i) for i in content]
        content = [re.sub('\r', '\n', i) for i in content]
        self.title = '\n\n'+title+'\n\n'
        self.content = content
        print(title, content)
12345678910111213141516171819202122232425262728293031323334353637383940

这两个类之中Spider用来爬取每一章的链接,相当于生产者,PageJob把生产者产出的每一章链接得到后就发起网页请求把每一章小说都下载下来,相当于消费者。这里我提一下第33-37行是在去除文章中的一些无关代码和加入一些调整格式的字符,其余的都是requests的最基本请求了,我就不提了,不懂的话就看下这份requests官方文档。

3.定义函数

def PutPageJob(para):
    q = para[0]
    i = para[1]
    links = para[2]
    q.put(PageJob(i, links[i]))
12345

这个函数其实没有增加实际作用,只是为了当我们在后面把函数放进线程时,能方便的传参数而已。

4.定义函数入点,开始执行

if __name__ == '__main__':
    start_time = time()
    spider = Spider()
    links = spider.get_page_urls()
    q = PriorityQueue()
    with ThreadPoolExecutor(max_workers=1000) as t:  # 创建一个最大容纳数量为1000的线程池
        obj_list = []
        links = links[12:]
        for i in range(len(links)):
            para = (q, i, links)
            p = t.submit(PutPageJob, para)
            obj_list.append(p)
        for future in as_completed(obj_list):
            data = future.result()
            print('*' * 50)
    while not q.empty():
        next_job = q.get()  # 可根据优先级取序列
        with open('死亡作业.txt', 'a', encoding='utf-8') as f:
            f.write(next_job.title)
            f.writelines(next_job.content)
    print('花费时间:', time()-start_time)
123456789101112131415161718192021

我们可以看到第十一行代码,我们用到了我们定义的那个函数,只有这样才能把参数传进去。第5-17行就是我们的重头戏,启用1000个线程的线程池,多线程并发执行我们的爬虫,并且把数据储存在了第5行我们实例化的PriorityQueue对象中,而且按照重要性排序,这里的重要性参数是i,i越小表示越先执行,因此我们写入时才可以先写入第1章这个样子。

总结

好啦,今天的教程就到这里!大家如果有其它的控制顺序的方法欢迎在评论区讨论!源码和这次爬取的txt文件下载地址我放在附件里咯!

附录

上次有人说没必要爬,有现成的txt可以下载,我想说的是,只有完结了的小说才有现成的txt,这些还在连载的小说是没有txt的,很多手机上的盗版小说app你可以点击下载或缓存某个小说,但是你会发现下载的很慢,每秒才几张,因为其实这根本就不是在下载小说,而是你点击下载后,app开启了一个单线程爬取那些书源网站,所以很慢。对比而来,这也正是本次1000线程爬虫的作用了。

源码获取加群:850591259

 

Python是一种灵活的编程语言,其提供了多种多样的库和框架,以方便用户处理数据和进行网络爬取。在网络爬取方面,Python具有优秀的单线程和多线程爬取能力。 Python线程爬取实例: 当我们需要爬取一个简单的网站时,单线程爬取可能是最简单和最有效的方法。例如,我们可以编写一个程序来爬取一个网站的所有页面,并将它们保存到本地文件夹中。这个程序可能像这样: ```Python import requests from bs4 import BeautifulSoup def getUrls(url): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') urls = [] for link in soup.find_all('a'): urls.append(link.get('href')) return urls def download(urls): for url in urls: response = requests.get(url) filename = url.split('/')[-1] with open(filename, 'wb') as f: f.write(response.content) if __name__ == '__main__': urls = getUrls('http://example.com') download(urls) ``` 在这个例子中,我们使用requests和BeautifulSoup库来获取和解析HTML页面,然后使用循环和文件I/O来保存页面内容。 Python线程爬取实例: 当我们需要爬取大量页面时,单线程爬取可能会非常缓慢,因此我们可以使用多线程爬取来提高效率。例如,我们可以使用Python的多线程库threading来实现多线程爬取。下面是代码示例: ```Python import requests from bs4 import BeautifulSoup import threading def getUrls(url): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') urls = [] for link in soup.find_all('a'): urls.append(link.get('href')) return urls def download(url): response = requests.get(url) filename = url.split('/')[-1] with open(filename, 'wb') as f: f.write(response.content) class CrawlerThread(threading.Thread): def __init__(self, url): threading.Thread.__init__(self) self.url = url def run(self): download(self.url) if __name__ == '__main__': urls = getUrls('http://example.com') threads = [] for url in urls: t = CrawlerThread(url) threads.append(t) t.start() for t in threads: t.join() ``` 在这个例子中,我们使用多线程CrawlerThread类来下载每个页面。我们创建一个CrawlerThread列表,然后将列表中的每个元素作为参数传递给download函数,以便每个线程都可以执行下载任务。最后,我们使用join方法等待所有线程完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值