多线程爬虫 爬取小说 速度真的快

文章目录

  • 前言
    • 获取每章url(解决html输出不全问题)
    • 提取标签
      • css选择器
    • 拼接url(标签中的url不完整的情况下)
  • 源码
    • spider.py
    • main.py
    • setting.py
    • item.py
  • 成果
  • 问题
  • 借鉴的文章(改一下就好多问题了)

前言

最近看了几篇关于多线程爬虫的文章,才发现我之前搞出来的那个爬虫速度真的是让人不忍直视,于是立马开始改造
只是没想到这一改,就几乎改头换面了。因为要多线程,就不能像之前那样从第一页出发,利用"下一页"按钮上的链接来逐章爬取,而是要先在书的目录那里获取全部章节的url
目的地:https://www.bige3.cc/xuanhuan/

获取每章url(解决html输出不全问题)

	article_url=[]
	
	#全局变量 gl_folder 文件夹名称
	#解决html输出不全问题(部分html过长,无法全部输出,进行对照) 
	html = gl_folder+'/'+bookname+".html"
	print(html)
	with open(html, "w", encoding="utf-8") as f:
		f.write(res.text)
	#先存再取
	with open(html, "r", encoding="utf-8") as f:
		html_content = f.read()
	#print(html_content)

提取标签

这一步要针对于需要的标签在网页源代码的分布选择合理的方法进行精准的提取

在这里插入图片描述

可以看到,具有href属性的a标签就是我们需要提取出来的
本次提取有几个问题需要解决:
1.有混淆的标签(图中圈出来的),其也位于dd标签中,并具有href属性
2.需要将全部章节url都提取出来,不再是之前的一个一个提取
3.目标分散,只有一小部分是< dt>< /dt>的孩子节点,大部分都是< span>< /span >的孩子结点,< dt>< /dt>的子孙结点

经过漫长的查找,才找到适配的法子(还不会正则表达式,准备以后学)

css选择器

tag.select(css)
tag bs4.element.Tag 对象
select 查找方法
css结构 [tagName][attName[=value]]

tagName 元素名称
attName 属性名称 默认所有元素
返回一个bs4.element.Tag的列表(即使只有一个元素)

本次的目标< a>标签全都是< dd>的孩子节点,所以

tags=soup.select(“dd a”)

上式可以提取出父母结点是< dd>的< a>标签,解决了(3)和(2)两个问题
但是这样子会把那个混淆的标签也提取出来,不利于后期爬取的进行,于是便再加上一些限制将其过滤掉

tags=soup.select(“dd a[href$=‘html’]”)

这样子就ok了

	# 解析html文档
	soup = BeautifulSoup(html_content, "html.parser")

	#取需要的标签(内含每章节的url)
	#详细解释请看https://blog.csdn.net/Heart_for_Ling/article/details/103650649
	tags=soup.select("dd a[href$='html']")

拼接url(标签中的url不完整的情况下)

	# 如果获取的标签为空,则表示没有章节
	if tags==[]:
		print("没有章节")
	# 遍历标签
	for tag in tags:
		# 获取标签的href属性
		page_url=tag['href']
		
		# 拼接标签的href属性
		#print(str(page_url))
		page_url=urllib.parse.urljoin(nxturl,page_url)
		
		# 打印拼接后的href属性
		print(page_url)
		
		# 将拼接后的href属性添加到article_url列表中
		article_url.append(page_url)

再将这些url和函数一起提交给线程池,这样才能实现多线程爬取

get_article() 通过每章节的url获取小说内容的函数

 # 使用线程池,最大1000个线程(有点卡啊,建议调小一点,我只是喜欢听风扇转的声音罢了)
        with ThreadPoolExecutor(max_workers=1000) as executor:
            # 将url和get_article函数作为参数,提交到线程池中
            future_to_url = {executor.submit(get_article, url): url for url in article_url}
            # 遍历线程池中的任务,获取结果
            for future in as_completed(future_to_url):
                # 获取url
                url = future_to_url[future]
                try:
                    # 获取结果
                    future.result()
                except Exception as exc:
                    # 打印异常信息
                    print(f'{url} generated an exception: {exc}')

源码

spider.py

from multiprocessing import Pool
import os
import random
import time
from bs4 import BeautifulSoup
import soupsieve
import item
import clean
import requests
import setting
from urllib.request import urlopen
import urllib.parse
from lxml import etree
from concurrent.futures import ThreadPoolExecutor, as_completed

##报错:HTTPSConnectionPool(host='www.bige3.cc', port=443): Max retries exceeded with url
# 但是可能会出现 InsecureRequestWarning 警告,
# 虽然不影响代码采集但是看着不舒服,可以加上下面两行:
import urllib3
urllib3.disable_warnings()

requests.adapters.DEFAULT_RETRIES = 5 # 增加重连次数

global gl_folder
gl_folder=''

#获取书的简介和每一章的url
def downbook(htt,folder):
    header=setting.headers
    shht=str(htt)
    print(shht)
    nxturl = item.nxturl
    print(nxturl)
    url = urllib.parse.urljoin(nxturl,shht)
    print(url)
    res = requests.get(url=url,headers=header,verify=False)
    t_res=res.content.decode('utf-8')
    print(t_res)
    htt_tree = etree.HTML(t_res)
    
    #提取标题
    bookname = htt_tree.xpath('/html/body/div[4]/div[2]/h1/text()')[0]
    author =htt_tree.xpath('/html/body/div[4]/div[2]/div[2]/span[1]/text()')[0]
    update=htt_tree.xpath('/html/body/div[4]/div[2]/div[2]/span[3]/text()')[0]
    introduction=htt_tree.xpath('/html/body/div[4]/div[2]/div[4]/dl/dd/text()')[0]
      
    print('==  《' + bookname + '》\r\n')  # 标题
    print('==  ' + author + '\r\n')     # 作者
    print('==  ' + update + '\r\n')     # 最后更新时间
    print("=" * 10)
    print('\r\n')
    print('==  ' + introduction + '\r\n')     # 简介
    print("=" * 10)
    print('\r\n')
    choose=input('是否下载?(y/n)')
    if choose=='y':
         # 创建文件
        global gl_folder
        gl_folder= folder+'/' + bookname
        try:
                os.makedirs(gl_folder)
        except Exception:
                print('文件已创建!,将保存进其中')
    
        book_introduction(gl_folder,[bookname, author, update, introduction])
        article_url=[]
        #获取每一章url
    
        #解决html输出不全问题(部分html过长,无法全部输出) 
        html = gl_folder+'/'+bookname+".html"
        print(html)
        with open(html, "w", encoding="utf-8") as f:
            f.write(res.text)
        #先存再取
        with open(html, "r", encoding="utf-8") as f:
            html_content = f.read()
        #print(html_content)
        # 解析html文档
        soup = BeautifulSoup(html_content, "html.parser")
        
        #取需要的标签
        #详细解释请看https://blog.csdn.net/Heart_for_Ling/article/details/103650649
        tags=soup.select("dd a[href$='html']")
        # 如果获取的标签为空,则表示没有章节
        if tags==[]:
            print("没有章节")
        # 遍历标签
        for tag in tags:
            # 获取标签的href属性
            page_url=tag['href']
            # 拼接标签的href属性
            print(str(page_url))
            page_url=urllib.parse.urljoin(nxturl,page_url)
            # 打印拼接后的href属性
            print(page_url)
            # 将拼接后的href属性添加到article_url列表中
            article_url.append(page_url)
        # 使用线程池,最大1000个线程(有点卡啊,建议调小一点,我只是喜欢听风扇转的声音罢了)
        with ThreadPoolExecutor(max_workers=1000) as executor:
            # 将url和get_article函数作为参数,提交到线程池中
            future_to_url = {executor.submit(get_article, url): url for url in article_url}
            # 遍历线程池中的任务,获取结果
            for future in as_completed(future_to_url):
                # 获取url
                url = future_to_url[future]
                try:
                    # 获取结果
                    future.result()
                except Exception as exc:
                    # 打印异常信息
                    print(f'{url} generated an exception: {exc}')
        # 打印完成信息
        print("complete!")
    else:
        print('已跳过,将进入下一本小说')

 
   

#书的简介
def book_introduction(folder,info):
     # 书的介绍       
    fp = open(folder+'/'+info[0]+'_简介'+'.txt', 'w+', encoding='utf-8')
    # 首先在新的文档中写入书籍信息
    fp.write('==  《' + info[0] + '》\r\n')  # 标题
    fp.write('==  ' + info[1] + '\r\n')     # 作者
    fp.write('==  ' + info[2] + '\r\n')     # 最后更新时间
    fp.write("=" * 10)
    fp.write('\r\n')
    fp.write('==  ' + info[3] + '\r\n')     # 简介
    fp.write("=" * 10)
    fp.write('\r\n')
    
    book_sleep()

    
#下载每章内容   
def get_article(url):
    header=setting.headers
    nxturl=item.nxturl
    #报错:HTTPSConnectionPool(host='www.bige3.cc', port=443): Max retries exceeded with url
    #6.使用session
    session=requests.session()
    try:
            resp = session.get(url, header,verify=False)  # 超时设置为10秒
    except:
            for i in range(4):  # 循环去请求网站
                resp = session.get(url, header, timeout=20,verify=False)
                if resp.status_code == 200:
                        break
    
    text = resp.content.decode('utf-8')
    html = etree.HTML(text)
    title = html.xpath('//*[@class="content"]/h1/text()')[0]
    texts = html.xpath('//*[@id="chaptercontent"]/text()')
    contents=contents.replace("请收藏本站:https://www.bige3.cc。笔趣阁手机版:https://m.bige3.cc", "")
    

    global gl_folder
    file_name = f"{gl_folder}/{title}"
    #不写入第一行(是标题,重复了,不好看)
    i=1
    with open(file_name + '.txt', 'a', encoding='utf-8') as f_new:
        f_new.write(title) # 写入新的章节标题
        f_new.write('\n')
        for content in contents:
            if i==0:
                f_new.write(content)
                f_new.write('\n\n')
            else:
                i=0    
        f_new.close( )
    
    book_sleep()
    
def book_sleep():
    sleepTime = random.randint(1, 3)  # 产生一个2~5之间的随机数
    time.sleep(sleepTime)             # 暂停2~5之间随机的秒数

main.py

import os
import requests
import item
from lxml import etree
import setting
import clean
import spider

#自动翻页,获取每本书的目录url
def book_store(down_num,folder):
        header=setting.headers
        for i in range(1,down_num+1):
                url=item.url+str(i)+'.html'
                session=requests.session()
                response = session.get(url=url,headers=header)

                # 将网页内容按utf-8规范解码为文本形式
                text = response.content.decode('utf-8')

                # 将文本内容创建为可解析元素
                html = etree.HTML(text) # type: ignore
                ht=html.xpath('//div[@class="image"]/a/@href')

                #拼接网页地址的例子
                '''
                url = 'https://www.bige3.cc/'
                get_url = 'book/110697/'
                next_url = urllib.parse.urljoin(url , get_url )
                '''

                # 遍历所有书籍目录地址
                for htt in ht:
                        spider.downbook(htt,folder)

if __name__=='__main__':
        folder_name=input('请输入存储图片的文件夹名称:')
        folder=folder_name+'/'
        try:
                os.makedirs(folder)
        except:
                print('文件夹已创建,将保存进其中')
        down_num=int(input('请输入要下载的页数:'))
        book_store(down_num,folder) 
        print('爬取成功')

clean.py

# 数据清洗函数
import os
def clean_data(file_path, title,folder):
    """
    :param filename: 原文档名
    :param info: [bookTitle,
    """

    #print("\n==== 数据清洗开始 ====")

    # 新的文件名
    chapter_title ="精修版_"+title+".txt"
    #new_filename ="new"+filename
    #保存到文件夹里
    new_file_path = f"{folder}/{chapter_title}"

    # 打开两个文本文档
    f_old = open(file_path, 'r', encoding='utf-8')
    f_new = open(new_file_path, 'w', encoding='utf-8')
    lines = f_old.readlines()  # 按行读取原文档中的内容
    empty_cnt = 0  # 用于记录连续的空行数

    # 遍历原文档中的每行
    for line in lines:
        if line == '\n':        # 如果当前是空行
            empty_cnt += 1      # 连续空行数+1
            if empty_cnt >= 2:  # 如果连续空行数不少于2
                continue        # 直接读取下一行,当前空行不写入
        else:                   # 如果当前不是空行
            empty_cnt = 0       # 连续空行数清零
        if line.startswith("\u3000\u3000"):  # 如果有段首缩进
            line = line[2:]                  # 删除段首缩进
            f_new.write(line)                # 写入当前行
        elif line.startswith("第"):          # 如果当前行是章节标题
            f_new.write(line)                # 写入章节标题
            #f_new.write("\n")                # 写入换行
            #f_new.write("-" * 20)            # 写入20个'-'
            f_new.write("\n")                # 写入换行
        elif line.startswith("请收藏"):
            print("")
        else:                                # 如果当前行是未缩进的普通段落
            f_new.write(line)                # 保持原样写入
    

    f_old.close()
    f_new.close()
    #AI说的:
    #使用try和except语句来尝试删除文件。如果在删除文件时发生错误,将捕获异常
    #并输出错误信息。如果文件不存在,输出提示信息。这样可以确保程序在删除文件
    #失败时不会中断,而是继续运行。
    try:
        os.remove(file_path)  #去除旧文件
    except:
        print("删除文件失败")
    #print(f"\n==== 数据清洗完成,新文件已保存到 {new_file_path} ====")

setting.py

#报错:HTTPSConnectionPool(host='www.bige3.cc', port=443): Max retries exceeded with url
#1.requests默认是keep-alive的,可能没有释放,加参数 headers={'Connection':'close'}
#2.response = requests.get(fpath_or_url,headers=headers,stream=True, verify=False)
#3.反复请求
#4.sleep()
#5.随机User Agent
#具体链接:https://blog.csdn.net/weixin_45520735/article/details/115260374

headers= {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0',
    'Cookie': 'hm=2ddbda39dfd88aa4b3ff183cae79e636',
    #'Host': '',
    #如果requests连接数很多,那么在请求中避免使用持久连接
    'Connection': 'close',
    'authority': 'www.bige3.cc'
}

item.py

#拼接网页url
nxturl = 'https://www.bige3.cc/book/'

#书库
url='https://www.bige3.cc/xuanhuan/'

成果

在这里插入图片描述

跟之前爬虫的那个速度真的是一个天,一个地啊

问题

可能是我玩过火了,爬了一个多小时就不行了,ip好像被封了,直接卡在main.py那里进不去。一到这一步就报错
在这里插入图片描述

明天我再试一下,看行不行

借鉴的文章(改一下就好多问题了)

从网页源代码中精准提取需要的标签
BeautifulSoup | Tag | select | 查找标签
html在vscode中显示不全,导致无法对照着找问题
requests或selenium获取网页内容不全问题(非异步加载)

未解决的
python3 错误 Max retries exceeded with url 解决方法

解决报错requests.exceptions.ConnectionError: HTTPSConnectionPool(host=‘xxx’, port=443): Max re

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值