bs4库爬取小说工具

news2025/1/15 17:51:44

学习了爬取天气预报,今天尝试做个爬取小说工具,有时候网上看看小说休闲下,打算保存txt文本文件,方便离线阅读。

第一步:先确定目标网址

网上随便找了本小说,先找到小说目录页面。

网址首页:'https://www.douyinxs.com'

目标小说目录页:'https://www.douyinxs.com/bqg/1081818/'

第二步:再定位章节目录url

按F12,确定章节位置。

 章节目录url定位: "div#list > dl > dd > a"

第三步:再定位章节明细内容

按F12,确定章节内容位置。

第四步:编写代码

采用2步爬取策略:

1,先爬取小说章节目录url和标题信息保存json文件。

2,再读取章节目录json文件,循环一条条爬取章节明细内容信息,写入txt文件中。

3,章节明细数量太多,很容易出现异常情况,增加了异常重发处理机制。

requests_bs4.py :

# -*- coding: UTF-8 -*-
# 爬取静态网页工具
import requests
import time
import re
from bs4 import BeautifulSoup
import random
import json
import os


def get_html_text(url):
    '''
    @方法名称: 获取网页的html信息
    @中文注释: 获取网页的html信息,转换成字符串格式数据
    @入参:
        @param url str 网址
    @出参:
        @返回状态:
            @return 0 失败或异常
            @return 1 成功
        @返回错误码
        @返回错误信息
        @param rsp_text str 网页html信息
    @作    者: PandaCode辉
    @创建时间: 2023-09-05
    @使用范例: get_html_text('https://www.baidu.com/')
    '''
    try:
        if (not type(url) is str):
            return [0, "111111", "网址参数类型错误,不为字符串", [None]]

        # 浏览器用户信息列表
        user_agents = [
            'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
            'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
            'Opera/9.25 (Windows NT 5.1; U; en)',
            'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
            'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
            'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
            'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
            'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7',
            'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0',
        ]
        # 随机获取一个浏览器用户信息
        agent = random.choice(user_agents)
        # header头信息
        headers = {
            'User-Agent': agent,
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
            'Accept-Encoding': 'gzip, deflate',
            'Connection': 'keep-alive',
            'Cache-Control': 'max-age=0',
        }
        # 代理IP地址,需要找到可用的代理ip,不然无法使用
        # proxy = {'HTTP': 'xx.xx.xx.xx:8000', 'HTTPS': 'xx.xx.xx.xx:80'}
        # response = requests.get(url, headers=headers, proxies=proxy, timeout=30)
        # 增加随机模拟浏览器访问header头信息,提高反爬网站成功率
        response = requests.get(url, headers=headers, timeout=30)
        # print(response.status_code)
        response.raise_for_status()
        response.encoding = 'utf-8'
        rsp_text = response.text
        # 返回容器
        return [1, '000000', '获取网页的html信息成功', [rsp_text]]

    except Exception as e:
        print("获取网页的html信息异常," + str(e))
        return [0, '999999', "获取网页的html信息异常," + str(e), [None]]



def spider_novel_mulu(req_dict):
    '''
    @方法名称: 爬取小说章节目录
    @中文注释: 根据参数爬取小说目录,保存到json文件
    @入参:
        @param req_dict dict 请求容器
    @出参:
        @返回状态:
            @return 0 失败或异常
            @return 1 成功
        @返回错误码
        @返回错误信息
        @param rsp_dict dict 响应容器
    @作    者: PandaCode辉
    @创建时间: 2023-09-06
    @使用范例: spider_novel_mulu(req_dict)
    '''

    try:
        if (not type(req_dict) is dict):
            return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
        # 目录分页,1-首页,2-下一页
        mulu_page = '1'
        url = req_dict['mulu_url']
        # 章节目录汇总列表
        tar_dir_href_list = []
        i = 1
        del_index = 0
        # 遍历目录分页
        while (True):
            # 根据url地址获取网页信息
            rst = get_html_text(url)
            if rst[0] != 1:
                return rst
            html_str = rst[3][0]
            # 使用BeautifulSoup解析网页数据
            soup = BeautifulSoup(html_str, "html.parser")

            # 目录列表地址
            tar_dir_href = soup.select(req_dict['tar_dir_href'])
            # print(tar_dir_href)
            # tar_len = len(tar_dir_href)
            # print('初始爬取章节总数量:', tar_len)
            # 目录分页,1-首页,2-下一页
            if mulu_page == '1':
                for dir_href in tar_dir_href:
                    chap_title = dir_href.text
                    # print(chap_title)
                    if '第1章' in chap_title:
                        break
                    del_index += 1
                # 过滤章节下标
                print(del_index)
                # 过滤章节,从第一章开始
                tar_dir_href_list = tar_dir_href[del_index:]
                tar_len = len(tar_dir_href_list)
                print('过滤后章节总数量:', tar_len)
                # 目录分页,1-首页,2-下一页
                mulu_page = '2'
            else:
                # 过滤章节,从第一章开始
                tar_dir_href = tar_dir_href[del_index:]
                # 判断一个list是否包含另一个list的全部元素
                is_in_list = [False for tmp in tar_dir_href if tmp not in tar_dir_href_list]
                if is_in_list:
                    print('tar_dir_href_list 不包含 tar_dir_href 的所有元素!可以合并添加列表')
                    # 合并列表
                    tar_dir_href_list.extend(tar_dir_href)
                    tar_len = len(tar_dir_href)
                    print('过滤后章节总数量:', tar_len)
                else:
                    print('tar_dir_href_list 包含 tar_dir_href 的所有元素!跳出循环,结束。')
                    break
            # 目录下一页url
            # 'https://www.douyinxs.com/bqg/1081818_10/'
            i += 1
            url = req_dict['mulu_url'][:-1] + '_' + str(i) + '/'
            print('目录下一页url: ' + url)
        tar_len = len(tar_dir_href_list)
        print('合并后章节总数量:', tar_len)
        # 目录容器
        mulu_dict = {}
        # 章节标题,列表
        mulu_dict['chap_title'] = []
        # 章节url,列表
        mulu_dict['chap_url'] = []
        # 是否完成标志: 0-未完成,1-已完成,列表
        mulu_dict['flag'] = []
        # 循环读取章节
        for dir_href in tar_dir_href_list:
            # 章节标题
            chap_title = dir_href.text
            print(chap_title)
            mulu_dict['chap_title'].append(chap_title)
            # 章节url
            chap_url = req_dict['novel_url'] + dir_href['href']
            print(chap_url)
            mulu_dict['chap_url'].append(chap_url)
            mulu_dict['flag'].append('0')
        # 转换为json字符串
        json_str = json.dumps(mulu_dict)
        json_name = req_dict['novel_name'] + '.json'
        # 写入json文件
        with open(json_name, 'w', encoding="utf-8") as json_file:
            json_file.write(json_str)
        # 返回容器
        return [1, '000000', '爬取小说目录成功', [None]]

    except Exception as e:
        print("爬取小说目录异常," + str(e))
        return [0, '999999', "爬取小说目录异常," + str(e), [None]]


def spider_novel_content(req_dict):
    '''
    @方法名称: 爬取小说章节明细内容
    @中文注释: 读取章节列表json文件,爬取小说章节明细内容,保存到文本文件
    @入参:
        @param req_dict dict 请求容器
    @出参:
        @返回状态:
            @return 0 失败或异常
            @return 1 成功
        @返回错误码
        @返回错误信息
        @param rsp_dict dict 响应容器
    @作    者: PandaCode辉
    @创建时间: 2023-09-06
    @使用范例: spider_novel_content(req_dict)
    '''

    try:
        if (not type(req_dict) is dict):
            return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
        # 章节目录文件名
        json_name = req_dict['novel_name'] + '.json'
        # 检查文件是否存在
        if os.path.isfile(json_name):
            print('json文件存在,不用重新爬取小说目录.')
        else:
            print('json文件不存在')
            # 爬取小说目录
            spider_novel_mulu(req_dict)
        # 读取json文件
        with open(json_name, 'r') as f:
            data_str = f.read()
        # 转换为字典容器
        mulu_dict = json.loads(data_str)
        """
            关于open()的mode参数:
            'r':读
            'w':写
            'a':追加
            'r+' == r+w(可读可写,文件若不存在就报错(IOError))
            'w+' == w+r(可读可写,文件若不存在就创建)
            'a+' ==a+r(可追加可写,文件若不存在就创建)
            对应的,如果是二进制文件,就都加一个b就好啦:
            'rb'  'wb'  'ab'  'rb+'  'wb+'  'ab+'
        """
        file_name = req_dict['novel_name'] + '.txt'
        # 在列表中查找指定元素的下标,未完成标志下标
        flag_index = mulu_dict['flag'].index('0')
        print(flag_index)
        # 未完成标志下标为0,则为第一次爬取章节内容,否则已经写入部分,只能追加内容写入文件
        # 因为章节明细内容很多,防止爬取过程中间中断,重新爬取,不用重复再爬取之前成功写入的数据
        if flag_index == 0:
            # 打开文件,首次创建写入
            fo = open(file_name, "w+", encoding="utf-8")
        else:
            # 打开文件,再次追加写入
            fo = open(file_name, "a+", encoding="utf-8")
        # 章节总数
        chap_len = len(mulu_dict['chap_url'])
        # 在列表中查找指定元素的下标
        print('章节总数:', chap_len)
        out_flag = False
        # 循环读取章节
        for i in range(flag_index, chap_len):
            # 章节标题
            chap_title = mulu_dict['chap_title'][i]
            print(chap_title)
            # 写入文件,章节标题
            fo.write(chap_title + "\r\n")
            # 章节url
            chap_url = mulu_dict['chap_url'][i]
            # 章节内容分页数
            for j in range(2):
                if j > 0:
                    # 章节内容下一页url
                    chap_url = mulu_dict['chap_url'][i][:-5] + '_' + str(j + 1) + '.html'
                print(chap_url)
                # 再爬取明细章节内容
                # 根据url地址获取网页信息
                chap_rst = get_html_text(chap_url)
                if chap_rst[0] != 1:
                    # 跳出循环爬取
                    out_flag = True
                    break
                chap_html_str = chap_rst[3][0]
                # 使用BeautifulSoup解析网页数据
                chap_soup = BeautifulSoup(chap_html_str, "html.parser")
                # 章节内容
                chap_content = chap_soup.select(req_dict['chap_content'])[0].text
                chap_content = chap_content.replace('    ', '\n').replace('  ', '\n')
                # print(chap_content)
                # 写入文件,章节内容
                fo.write(chap_content + "\r\n")
            # 跳出循环爬取标志
            if out_flag:
                print("跳出循环爬取标志")
                break
            # 爬取明细章节内容成功后,更新对应标志为-1-已完成
            mulu_dict['flag'][i] = '1'
        # 关闭文件
        fo.close()
        print("循环爬取明细章节内容,写入文件完成")
        # 转换为json字符串
        json_str = json.dumps(mulu_dict)
        # 再次写入json文件,保存更新处理完标志
        with open(json_name, 'w', encoding="utf-8") as json_file:
            json_file.write(json_str)
        print("再次写入json文件,保存更新处理完标志")
        # 返回容器
        return [1, '000000', '爬取小说内容成功', [None]]

    except Exception as e:
        # 转换为json字符串
        json_str = json.dumps(mulu_dict)
        # 再次写入json文件,保存更新处理完标志
        with open(json_name, 'w', encoding="utf-8") as json_file:
            json_file.write(json_str)
        print("再次写入json文件,保存更新处理完标志")
        print("爬取小说内容异常," + str(e))
        return [0, '999999', "爬取小说内容异常," + str(e), [None]]


# 主方法
if __name__ == '__main__':


    req_dict = {}
    req_dict['novel_name'] = '重生八八从木匠开始'
    req_dict['novel_url'] = 'https://www.douyinxs.com'
    # 目录页面地址,第1页
    req_dict['mulu_url'] = 'https://www.douyinxs.com/bqg/1081818/'
    # 章节目录定位
    req_dict['tar_dir_href'] = "div#list > dl > dd > a"
    # 章节明细内容定位
    req_dict['chap_content'] = 'article#content'
    # 爬取小说目录
    # spider_novel_mulu(req_dict)
    # 爬取小说内容
    spider_novel_content(req_dict)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/998539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c语言练习48:总结字符函数和字符串函数

总结字符函数和字符串函数 字符函数 1. 字符分类函数 C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的。 这些函数的使⽤都需要包含⼀个头⽂件是 ctype.h 2. 字符转换函数 字符串函数 . strlen的使⽤ •字符串以 \0 作为结束标…

Linux操作系统基础知识

目录 一、什么是Linux 二、如何有一个Linux环境 三、基本的 Linux 命令 1. pwd - 显示当前工作目录 2. ls - 列出文件和目录 3. cd - 切换目录 4. mkdir - 创建目录 5. rm - 删除文件或目录 6. cp - 复制文件或目录 7. mv - 移动文件或目录 8. touch - 创建空文件 9…

智能座舱概述

文章目录 智能座舱智能驾驶一、汽车座舱历经机械化、电子化,向智能化不断演进二、智能座舱的定义:车内升级车外互联1.从车内看2.从车外看 三、电子座舱、智能助理、人机共驾、第三生活空间 智能座舱智能驾驶 智能汽车以“座舱”“底盘”上下两大智能化系…

短视频去水印

一、使用方法 打开短视频APP, 选择要下载的视频,点击右下角分享按钮,在分享弹框中点击“复制链接” 将刚才复制的链接粘贴到下面的输入框(中文可以不用去掉) 二、短视频解析王源码 public function analysis($video…

音视频技术开发周刊 | 310

每周一期,纵览音视频技术领域的干货。 新闻投稿:contributelivevideostack.com。 学术头条 | 基于网络科学的人工智能揭示基因信息如何利用单细胞塑造形体 近日,由清华大学脑与智能实验室复杂网络智能中心(CCNI)主任Ca…

监听对象中属性变化(一个或多个属性、全部属性)

一、数据监听器 什么是数据监听器 数据监听器用于监听和响应任何属性和数据自动的变化,从而执行特定的操作。它的作用类似于vue中的watch侦听器。在小程序中,基本语法格式如下: Component({observers: {字段A,字段B: function(字…

计算机丢失msvcp140.dll是什么意思?msvcp140.dll丢失的解决方法

在使用计算机的过程中,我们可能会遇到各种奇葩的问题。其中,一个常见的问题是计算机提示丢失msvcp140.dll。这个文件是Microsoft Visual C 2015 Redistributable的一部分,通常用于支持一些软件(如游戏、办公软件等)的运…

遗忘因子递推最小二乘参数估计(FFRLS)

基于遗忘因子的最小二乘法电池参数辨识 最小二乘法是系统辨识中最常用的一种估算方法。为了克服最小二乘法存在”数据饱和”的问题,我们通常采用含有遗忘因子的递推最小二乘法(Forgetting Factor Recursive Least Square,FFRLS)算法进行电池模型的参数辨识。 1、二…

图片码二次渲染绕过

目录 一、环境 1、代码 2、文件处理方式 3、图片码的制作 二、绕过图片重构 1、可行性分析 2、数据比对 3、完成绕过 一、环境 以upload-labs靶场第十七关为例 1、代码 源码为&#xff1a; <?php include ../config.php; include ../head.php; include ../menu.…

管理类联考——数学——汇总篇——知识点突破——应用题——分段计费

👊 分段计费是指不同的范围对应着不同的计费方式,在实际中应用很广泛,比如电费,水费、邮费、个税、话费、出租车费、销售提成等等。解题思路的关键点有两个,一个是先计算每个分界点的值,确定所给的数值落入哪个范围;另外,对应选取正确的计费表达式,按照所给的标准进…

腾讯云CVM S5服务器性能测评和租用价格1年和五年

腾讯云服务器CVM五年时长&#xff0c;2核2G服务器5年1728元、2核4G1M带宽五年3550、4核8G服务器6437元五年&#xff0c;CVM标准型S5实例可选2核2G、2核4G和4核8M&#xff0c;公网带宽可1M、3M和5M&#xff0c;系统盘为50G高性能云硬盘&#xff0c;S5云服务器CPU采用Intel Xeon …

模电课设:用Multisim简单了解二极管

1 课设内容 1&#xff09;测试二极管伏安特性电路&#xff1b; 2&#xff09;二极管的整流电路及负载对输出电压和纹波的影响&#xff1b; 2 模型搭建 电路一&#xff1a;测试二极管伏安特性的电路如下图所示&#xff0c;结构十分简单&#xff0c;直流电源串联上二极管组成一…

windows10搭建llama大模型

背景 随着人工时代的到来及日渐成熟&#xff0c;大模型已慢慢普及&#xff0c;可以为开发与生活提供一定的帮助及提升工作及生产效率。所以在新的时代对于开发者来说需要主动拥抱变化&#xff0c;主动成长。 LLAMA介绍 llama全称&#xff1a;Large Language Model Meta…

c#中字段和属性的区别,委托和事件的区别

IDE眼里的字段和属性 class Test {public int age1 12;public int Age2 { get; set; } 18;public void Show(){Console.WriteLine(age1);Console.WriteLine(Age2);} }很多新人发现在类中定义变量时&#xff0c;有些人会在后面写上get,set。 这种写法定义出来的变量&#xf…

数据结构与算法-二叉搜索树红黑树

一&#xff1a;二叉搜索树 大家来看以下几个结构&#xff1a;下图中的 二叉搜索树又叫二叉查找树&#xff0c;二叉排序树&#xff1b; 它具有以下特点&#xff1a; 1.如果它的左子树不为空&#xff0c;则左子树上结点的值都小于根结点。 2.如果它的右子树不为空&#xff0c;则右…

动手学深度学习——Windows下的环境安装流程(一步一步安装,图文并配)

目录 环境安装官网步骤图文版安装Miniconda下载包含本书全部代码的压缩包使用conda创建虚拟&#xff08;运行&#xff09;环境使用conda创建虚拟环境并安装本书需要的软件激活之前创建的环境打开Jupyter记事本 环境安装 文章参考来源&#xff1a;http://t.csdn.cn/tu8V8 官网…

编程初学者指南(2023版):零基础小白如何学习编程-两万字详述

文章目录 1.写在前面1.1 为什么有这份指南1.2 指南里有什么1.3 关于软件协会1.4 面对人生&#x1f340; 对工作&#xff1a;越努力越幸运&#x1f340; 对感情&#xff1a;爱得厚重开阔&#x1f340; 对他人&#xff1a;保持尊重、友好、真诚和谦逊&#x1f340; 对生活&#x…

【论文解读】元学习:MAML

一、简介 元学习的目标是在各种学习任务上训练模型&#xff0c;这样它就可以只使用少量的训练样本来解决新任务。 论文所提出的算法训练获取较优模型的参数&#xff0c;使其易于微调&#xff0c;从而实现快速自适应。该算法与任何用梯度下降训练的模型兼容&#xff0c;适用于…

群辉 Synology NAS Docker 安装 RustDesk-server 自建服务器只要一个容器

from https://blog.zhjh.top/archives/M8nBI5tjcxQe31DhiXqxy 简介 之前按照网上的教程&#xff0c;rustdesk-server 需要安装两个容器&#xff0c;最近想升级下版本&#xff0c;发现有一个新镜像 rustdesk-server-s6 可以只安装一个容器。 The S6-overlay acts as a supervi…

【Proteus仿真】【STM32单片机】便携式血糖仪

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 系统运行后&#xff0c;LCD1602显示开机界面信息&#xff0c;当按下K1键开始测量&#xff0c;步进电机运行启动针头采血&#xff0c;然后检测血糖值显示在屏幕上&#xff1b;如果血糖高于上限&#xff0c…