bs4库爬取小说工具

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

第一步:先确定目标网址

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

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

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

bs4库爬取小说工具_第1张图片

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

按F12,确定章节位置。

bs4库爬取小说工具_第2张图片

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

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

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

bs4库爬取小说工具_第3张图片

第四步:编写代码

采用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)

bs4库爬取小说工具_第4张图片

你可能感兴趣的:(python,爬虫)