Python+bs4实现爬取小说并下载到本地

2 篇文章 0 订阅
2 篇文章 0 订阅

Python+bs4实现爬取小说并下载到本地


前言

在公司闲的无聊之际,想研究研究python的bs模块,试着写一写爬虫。但是公司有限制,娱乐网址一律不能访问,最后发现小说网站还能进,那就你了。开整~

以前觉得这东西挺low的,从页面上抓取数据什么的我一直都觉得没啥意思,不过今天我居然开始感觉到了一些成就感。

一、引包

本次爬虫主要用到了两个库:

import requests
from bs4 import BeautifulSoup

requests模块用于模拟请求,获取响应页面;bs4模块用于解析响应的页面,方便获取页面标签。

二、代理问题

本来想先试试水,用简单的代码试试能不能访问页面,结果一试就出现了下面的问题:

Traceback (most recent call last):
  File "D:/PycharmProjects/NovelCrawling/novel_crawling.py", line 109, in pre_op
    book_info = search_by_kewords(keword)
  File "D:/PycharmProjects/NovelCrawling/novel_crawling.py", line 89, in search_by_kewords
    soup = BeautifulSoup(result_html, 'lxml')
  File "D:\python\lib\site-packages\bs4\__init__.py", line 310, in __init__
    elif len(markup) <= 256 and (
TypeError: object of type 'NoneType' has no len()
HTTPSConnectionPool(host='www.13800100.com', port=443): Max retries exceeded with url: /index.php?s=index/search&name=%E6%96%97%E7%BD%97%E5%A4%A7%E9%99%86&page=1 (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000002029189FD30>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))

好奇怪的嘞,浏览器能正常访问,而且在公司但也设置了代理,怎么就请求不到呢。带着这个疑问就是一番百度,最多的结果是像这样:

response = requests.get(url, verify=False)

带上verify=False参数,说什么关闭openSSL验证啥的,咱也不懂啊,只能验证了一下,很遗憾,未果。
还有人说很多情况都是地址拼错了的问题,但我仔细检查了也没有。

难受,百度了半个小时也没能找到自己想要的答案,都想着算了,无聊就无聊吧,玩儿手机也挺香的。但是转念一想,是不是requests模块要单独设代理呢?

带着这个疑问再次进行了百度,requests模块还真能设置代理,感觉有了希望,最后成功解决!代码如下:

proxies = {
    'http': 'http://xxx.xxx.xxx.xxx:xx', # http代理
    'https': 'http://xxx.xxx.xxx.xxx:xx' # https代理
}
response = requests.get(url, verify = False, proxies = proxies)

这里还get到一个点,上面的配置意思是说,当请求是http的时候走http的代理,是https请求的时候走https的代理,但是并不是意味着https代理一定要是https的地址。 就像我这里两个代理都是设置的http代理地址。有一点点绕,其实简单来说,就是当请求是https的时候也走我设置的http代理。

当然你自己在家使用就没那么多蛋疼的问题了。

三、爬取过程

本次爬取的小说网站是138看书网https://www.13800100.com,分析阶段就不在这记录了,主要就是分析网页定位需要信息的元素问题。这里主要记录一下大概的思路:

其实感觉还是挺简单的,总的来说就三步:

1、获取章节列表,分析每一章节的下载网页
2、分析下载网页,获取每一章节的小说内容
3、将小说内容存储到文本文件中

1、获取章节列表
分析小说目录章节网页,大概长这样子:
在这里插入图片描述

通过F12开发工具分析出章节的元素位置,我们的目的是要分析出每一章节的阅读地址。这里可以根据css定位然后进行分析:

# 从网页内容中提取章节链接
def get_download_page_urls(htmlContent):
    # 实例化soup对象, 便于处理
    soup = BeautifulSoup(htmlContent, 'lxml')
    # 获取所有章节的a标签
    li_as = soup.select('.bd>ul>.cont-li>a')
    # 小说名称
    text_name = soup.select('.cate-tit>h2')[0].text
    # 下载地址
    dowload_urls = []
    for a in li_as:
        dowload_urls.append(f"{base_url}{a['href']}")
    return [text_name, dowload_urls]

我这里获取了小说的名称以及各章节的链接。


2、获取各章节的小说内容
各个章节的链接已经拿到了,接下来只需要分析阅读网页的内容并存储到文件中就可以了。网页长这样:
在这里插入图片描述
代码处理:

# 分章节下载
def download_by_chapter(article_name, url ,index):
	'''
	article:小说名称
	url:章节阅读链接
	index:章节序号
	'''
    content = get_content(url)
    soup = BeautifulSoup(content, 'lxml')
    # 章节标题
    title = soup.select('.chapter-page h1')[0].text
    # 作者部分处理
    author = soup.select('.chapter-page .author')[0].text.replace('\n', '')
    # 小说内容部分处理
    txt = soup.select('.chapter-page .note')[0].prettify().replace('<br/>', '')\
        .replace('\n', '')
    txt = txt[txt.find('>') + 1:].rstrip('</div>').replace('   ', '\n').strip()
    txt_file = open(fr"{article_name}\{'%04d' % index}_{title}.txt", mode='w',encoding='utf-8')
    txt_file.write(f'{title}\n\n{author}\n\n{txt}'.replace(' ', ' '))
    txt_file.flush()
    txt_file.close()

关于小说内容部分的处理,本来可以采用.text只获取文本内容,然后进行处理就可以了,但是操作后发现不太好分段落,还会有很多奇奇怪怪的符号。几经折腾最后还是选择了使用prettify()方法,将元素格式化成html字符串,然后进行相应的处理。
这里学到一点,关于数字的格式化:

'%04d' % index # 表示将index格式化成四位

有时候我们不想分文件下载,于是我加了一个下载到同一个文件中的方法:

# 下载到一个文件中
def download_one_book(txt_file, url, index):
	'''
	txt_file:存储文本对象
	url:章节阅读链接
	index:章节序号
	'''
    content = get_content(url)
    soup = BeautifulSoup(content, 'lxml')
    # 章节标题
    title = soup.select('.chapter-page h1')[0].text
    # 作者部分处理
    author = soup.select('.chapter-page .author')[0].text.replace('\n', '')
    # 小说内容部分处理
    txt = soup.select('.chapter-page .note')[0].prettify().replace('<br/>', '') \
        .replace('\n', '')
    txt = txt[txt.find('>') + 1:].rstrip('</div>').replace('   ', '\n').strip()
    txt_file.write(f'{title}\n\n'.replace(' ', ' '))
    # 只在第一章节写入作者
    if index == 0:
        txt_file.write(f'{author}\n\n'.replace(' ', ' '))
    txt_file.write(f'{txt}\n\n'.replace(' ', ' '))
    txt_file.flush()

四、代码扩展

很明显,程序有些僵硬,使用者必须要手动去138读书网找到小说的目录地址,才能通过本程序进行下载。所以我分析网页后,写出一个网站搜索小说的方法:

# 关键字查找书籍
def search_by_kewords(keyword, page=1):
	'''
	keyword:搜索关键字
	page:分页页号
	'''
    book_info = {}
    while True:
        search_url = f'{base_url}/index.php?s=index/search&name={keyword}&page={page}'
        result_html = get_content(search_url)
        soup = BeautifulSoup(result_html, 'lxml')
        books_a = soup.select('.s-b-list .secd-rank-list dd>a')
        for index, book_a in enumerate(books_a):
            book_info[f'{index + 1}'] = [book_a.text.replace('\n', '').replace('\t', ''), f"{base_url}{book_a['href']}".replace('book', 'list')]
        if len(books_a) == 0:
            break
        page += 1
    print(f'共查找到{page}页,{len(book_info.keys())}本书籍。')
    print('--------------------------')
    return book_info

然后做了一下细节的处理,得出了完整的程序:

import os
import sys
import time
import traceback
import warnings
import requests
from bs4 import BeautifulSoup

# 忽略警告
warnings.filterwarnings('ignore')
# 代理配置
proxies = {
    'http': 'http://xxx.xxx.xxx.xxx:xx', # http代理
    'https': 'http://xxx.xxx.xxx.xxx:xx' # https代理
}
# 138看书网地址
base_url = 'https://www.13800100.com'


# 获取目录网址内容
def get_content(url,):
    response = ''
    try:
        # user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36"
        response = requests.get(url, verify = False, proxies = proxies) # 需要使用代理的情况
        # response = requests.get(url, verify=False) # 不用代理的情况
        response.raise_for_status() # 如果返回的状态码不是200, 则抛出异常;
        return response.content.decode(encoding=response.encoding) # 解码后的网页内容
    except requests.exceptions.ConnectionError as ex:
        print(ex)


# 从网页内容中提取章节链接
def get_download_page_urls(htmlContent):
    # 实例化soup对象, 便于处理
    soup = BeautifulSoup(htmlContent, 'lxml')
    li_as = soup.select('.bd>ul>.cont-li>a')
    text_name = soup.select('.cate-tit>h2')[0].text
    dowload_urls = []
    for a in li_as:
        dowload_urls.append(f"{base_url}{a['href']}")
    return [text_name, dowload_urls]


# 分章节下载
def download_by_chapter(article_name, url ,index):
    content = get_content(url)
    soup = BeautifulSoup(content, 'lxml')
    # 章节标题
    title = soup.select('.chapter-page h1')[0].text
    # 作者部分处理
    author = soup.select('.chapter-page .author')[0].text.replace('\n', '')
    # 小说内容部分处理
    txt = soup.select('.chapter-page .note')[0].prettify().replace('<br/>', '')\
        .replace('\n', '')
    txt = txt[txt.find('>') + 1:].rstrip('</div>').replace('   ', '\n').strip()
    txt_file = open(fr"{article_name}\{'%04d' % index}_{title}.txt", mode='w',encoding='utf-8')
    txt_file.write(f'{title}\n\n{author}\n\n{txt}'.replace(' ', ' '))
    txt_file.flush()
    txt_file.close()


# 下载到一个文件中
def download_one_book(txt_file, url, index):
    content = get_content(url)
    soup = BeautifulSoup(content, 'lxml')
    # 章节标题
    title = soup.select('.chapter-page h1')[0].text
    # 作者部分处理
    author = soup.select('.chapter-page .author')[0].text.replace('\n', '')
    # 小说内容部分处理
    txt = soup.select('.chapter-page .note')[0].prettify().replace('<br/>', '') \
        .replace('\n', '')
    txt = txt[txt.find('>') + 1:].rstrip('</div>').replace('   ', '\n').strip()
    txt_file.write(f'{title}\n\n'.replace(' ', ' '))
    # 只在第一章节写入作者
    if index == 0:
        txt_file.write(f'{author}\n\n'.replace(' ', ' '))
    txt_file.write(f'{txt}\n\n'.replace(' ', ' '))
    txt_file.flush()


# 关键字查找书籍
def search_by_kewords(keyword, page=1):
    book_info = {}
    while True:
        search_url = f'{base_url}/index.php?s=index/search&name={keyword}&page={page}'
        result_html = get_content(search_url)
        soup = BeautifulSoup(result_html, 'lxml')
        books_a = soup.select('.s-b-list .secd-rank-list dd>a')
        for index, book_a in enumerate(books_a):
            book_info[f'{index + 1}'] = [book_a.text.replace('\n', '').replace('\t', ''), f"{base_url}{book_a['href']}".replace('book', 'list')]
        if len(books_a) == 0:
            break
        page += 1
    print(f'共查找到{page}页,{len(book_info.keys())}本书籍。')
    print('--------------------------')
    return book_info


# 主程序处理
def pre_op():
    start = time.perf_counter()
    try:
        print(f'本程序适用于下载138看书网小说,138看书网: {base_url}')
        print('请输入关键字查找书籍:')
        keword = input()
        print('正在查找...')
        book_info = search_by_kewords(keword)
        print('请选择对应序号进行下载:')
        print('**********************')
        for index in book_info.keys():
            print(f"{index}: {book_info[index][0]}")
        print('**********************')
        c = input('请选择:')
        result_html = get_content(book_info[c][1])
        dowload_urls = get_download_page_urls(result_html)
        print('下载链接获取完毕!')
        print('----------------------')
        print('请选择下载模式:')
        print('1.分章节下载')
        print('2.整本下载')
        c = input()
        txt_file = ''
        if not os.path.exists(fr'./{dowload_urls[0]}'):
            os.mkdir(fr'./{dowload_urls[0]}')
        if c == '2':
            txt_file = open(fr"{dowload_urls[0]}\{dowload_urls[0]}_book.txt", mode='a+', encoding='utf-8')
        for index, dowload_url in enumerate(dowload_urls[1]):
            if c == '1':
                download_by_chapter(dowload_urls[0], dowload_url, index + 1)
            else:
                txt_file.write(f'{dowload_urls[0]}\n\n')
                download_one_book(txt_file, dowload_url, index)
            sys.stdout.write('%\r')
            percent = str(round(float(index + 1) * 100 / float(len(dowload_urls[1])), 2))
            sys.stdout.write(f'正在下载...{percent} %')
            sys.stdout.flush()
        txt_file.close()
        print(f'\n下载完毕!共计{len(dowload_urls[1])}章节.耗时:{str(round(time.perf_counter() - start, 2))}s.')
        print('=======================================================')
    except:
        traceback.print_exc()


if __name__ == '__main__':
    pre_op()

看一下最终的效果:

本程序适用于下载138看书网小说,138看书网: https://www.13800100.com
请输入关键字查找书籍:
斗罗大陆
正在查找...
共查找到4页,20本书籍。
--------------------------
请选择对应序号进行下载:
**********************
1: 斗罗大陆之冰凰斗罗
2: 斗罗大陆之我本蓝颜
3: 斗罗大陆之剑决天下
4: 斗罗大陆之极限
5: 斗罗大陆III龙王传说(龙王传说)
6: 斗罗大陆之青莲剑帝姬
7: 斗罗大陆3龙王传说
8: 斗罗大陆之神圣龙斗罗
9: 斗罗大陆之昊天传说
10: 斗罗大陆之白凤传奇
11: 斗罗大陆之红颜系统
12: 斗罗大陆之仙神纪
13: 斗罗大陆之时崎狂三
14: 斗罗大陆lll龙之御尘
15: 斗罗大陆之焰门传奇
16: 斗罗大陆国服达摩玉小刚
17: 斗罗大陆的魔法师
18: 斗罗大陆一剑倾世
19: 斗罗大陆之灵魂手笔
20: 斗罗大陆之倾尽天下
**********************
请选择:11
下载链接获取完毕!
----------------------
请选择下载模式:
1.分章节下载
2.整本下载
2
正在下载...100.0 %
下载完毕!共计153章节.耗时:220.17s.
=======================================================

在这里插入图片描述
至此,就是今日学习的成果了,本来想克服一下使用多线程提高一下下载速度,但是本人对多线程实在是很薄弱,折腾了也没弄出来。如果有谁能帮我改成多线程,感激不尽。

最后,欢迎大家留言一起探讨!有什么不合适的地方请指正。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Anesthesia丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值