笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道

支持正版,一切只为学习

之前写的笔趣阁爬虫有不少同学说不能爬了,我后来发现是网站改版的缘故,前些日子把书里的爬虫项目都整的差不多了,现在又有些不知道爬什么好了,刚好在这段时间把笔趣阁爬虫代码重写一下。
(PS:我看自己之前写的代码感觉写的好丑哦,官方吐槽,最为致命,以前的链接)

做了以下改进:

  1. 将爬虫代码写入对象中,把用户选择判断的语句写在外部,使得爬虫本身更加简洁;
  2. 优化互动的相关代码,操作更舒适;
  3. 采用多进程爬取小说各章节的内容,速度更快;
  4. 加入UA代理,尽可能反反爬虫,安全性更高 。

目录

  • 一、基本思路
  • 二、构建爬虫
    • 1、借东风,借用网站搜索书籍的功能
    • 2、遇明主,用户确认下载小说
    • 3、取荆州,下载小说内容,保存到docx文档中
  • 三、运行测试
  • 四、总结
  • 五、完整代码

前期准备,需要在cmd里下载安装以下第三方库:
pip install requests
pip install python-docx

一、基本思路

以下是爬取笔趣阁小说的主体思路,爬虫代码在这个思路上扩展构建。

Created with Raphaël 2.2.0 开始 输入书名 查询小说是否存在 跳转页面至小说主页 获取小说目录URL 解析小说内容并保存至word 结束 yes no

二、构建爬虫

笔趣阁的网站众多,这是我爬取的笔趣阁网址:http://www.biquge.tv/
我将爬虫写到 biqugeCrawl 类中。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第1张图片

1、借东风,借用网站搜索书籍的功能

以搜索圣墟为例,网站的搜索是靠这个searchkey这个参数。在biqugeCrawl类中定义一个search_book() 函数专用于查找小说功能。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第2张图片
1.1、 大家看到这个%什么的或许会很陌生,这是因为URL 只允许一部分 ASCII 字符(数字字母和部分符号),其他的字符(如汉字)是不符合 URL 标准的,所以我们这里使用 urllib库parse.quote() 将其转换为URL编码。

转换后还是不符合要求,怀疑是编码格式的问题,去网页的源码中查看,如下显示为GBK编码,所以我们输入的书名还要先转换为GBK格式。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第3张图片

name = input("请输入书名:")
name = parse.quote(name.encode('gbk'))

1.2、 接下来我们拼接搜索的url,并请求它,得到相应的搜索结果的html网页。

__random_ua()是随机返回user-agent的函数,这样在一定程度上可以避免被反爬。代码很简单,自己看一下。

serach_url = "http://www.biquge.tv/modules/article/search.php?searchkey={}"
url = serach_url.format(name)
response = requests.get(url=url, headers=self.__random_ua())

1.3、 如果搜到了书籍,也不直接下载,因为好多小说名字相似趁热度的,所以我这里获取第一个小说名及作者名返回让用户确认是否下载。另外,因为搜索不当,可能没有书籍,也可能是关键字太短了,我用try-except 另行处理,返回None值,并提示没有该书。

# 搜索书籍
root = etree.HTML(response.content)

try:
     book_name = root.xpath('//*[@id="nr"]/td[1]/a/text()')[0]

     book_author = root.xpath('//*[@id="nr"]/td[3]/text()')[0]

     book_url = root.xpath('//*[@id="nr"]/td[1]/a/@href')[0]

     return [book_name, book_author, book_url]

 except:
     return None
# 用户确认
book = biquge.search_book()
if book is not None:
    flag = input("搜索到的书名为《{}》,作者名为{},请确认是否下载【Y/N】".format(book[0], book[1]))
    while 1:
        if flag == 'Y' or flag == 'y':
            biquge.download(book[0], book[2])
            break
        elif flag == 'N' or flag == 'n':
            break
        else:
            flag = input("请正确输入【Y/N】!")
else:
    print("查无此书")

笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第4张图片
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第5张图片
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第6张图片

2、遇明主,用户确认下载小说

上一步写得search_book() 函数在搜到小说得情况下返回了三个数据:小说名、作者名、小说得url,经由用户确认,调用biqugeCrawl.download() ,获取小说章节链接,并采用多线程下载内容。

2.1、 请求网页,获取链接,因为它有九个最新章节得在里面,所以我们使用xpath解析出来得列表要略去这九个,直接用切片切掉。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第7张图片

response = requests.get(url=url, headers=self.__random_ua())
root = etree.HTML(response.content)

# 拼接完整得章节url 
content_urls = list(map(lambda x: "http://www.biquge.tv/" + x, root.xpath('//*[@id="list"]/dl/dd/a/@href')[9:]))
content_names = root.xpath('//*[@id="list"]/dl/dd/a/text()')[9:]

 # 创建以书名命名的文件夹
path = '《' + book_name + '》'
if not os.path.exists(path):
	os.mkdir(path)

2.2、 有了相应得链接,这里就要使用多进程了,速度大大的增加。

utls_names = [i for i in zip(content_urls, content_names)]

# 同时进行得进程数量
pool = Pool(processes=5)

# 将utls_names中得元素分配给parse()函数
pool.map(self.parse, utls_names)

3、取荆州,下载小说内容,保存到docx文档中

上面一步是获取到了小说章节的目录,并采用多进程的方法调用了biqugeCrawl.parse() 函数,这个函数的作用就是下载并保存小说内容。
3.1、 解析出小说的主体内容,观察网页源码,可以发现,小说的内容都在

html标签中,我用正则表达式将其解析出来,并对文本做了一些处理。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第8张图片

response = requests.get(url=url, headers=self.__random_ua())
html = response.content.decode('gbk')
content = "".join(re.findall('
(.*?)
'
, html, re.S)) content = re.sub("<.*?>", "", re.sub("  ", " ", content))

3.2、 获取到的小说内容肯定是要保存起来的,我这里保存到了docx文档文件中。这里又用xapth解析出了小说名,作为文本保存路径的一部分。

我后面想想那小说章节名也可以不传进来,在这里解析就是了,算了,无伤大雅

root = etree.HTML(response.content)
book_name = root.xpath('//div[@class="con_top"]/a[2]/text()')[0]

# 创建document对象
document = Document()

# 将文本写入文档
document.add_paragraph(content)

document.save("《{}》/《{}》.docx".format(book_name, name))

print('《' + name + '》已下载完成')

爬虫的思路都理清楚了,还有一些旁支末节详见完整代码,有什么不清楚也可以直接来问我。

三、运行测试

做了一些简单的测试,按照提示来操作都是没问题的,就算不按照提示来,整些奇奇怪怪的操作也是没问题的,很舒服,而且速度也快,我爬取《圣墟》一千五百多章大概耗时三分钟,也可以更快,不过我们只是学习爬虫的技术又不是搞破坏,太快的话对服务器的压力也很大的,还有被封IP的可能,可以参考【构建简单IP代理池】,使用代理ip,这样更安全。

IP代理池我自己是改了好几版了,用起来还是很舒服的,不用担心被封ip的事情了,把ip数据存到数据库中,上手简单,建议学习搭建。

笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第9张图片

四、总结

这爬取笔趣阁本来就是个小爬虫,我感觉很适合让刚入爬虫的同学学习的,虽然相比那种只爬取单个网页的入门学习方式要多些困难,不过克服困难后还是会有很大的进步的,这是我整理的【爬虫实战项目集合】,有兴趣的可以看看,由浅入深的学习爬虫,最简单的和较为复杂的都有。

对于这个爬笔趣阁的项目,我认为有一个很大的槽点,它是多进程爬取出来的,文件按时间排序不行,按名称排序又是下面这样,emm,太难看了,还是之前老版本的非多进程好看,但就是慢些,没有思路解决这个问题,求教。
笔趣阁爬虫(2020重制版),贴心的操作,谁用谁知道_第10张图片
另外,我想在爬虫里加上暂定下载的功能,以及断网处理的功能(这两个功能是一个原理,搞一个任务队列),这爬取笔趣阁小爬虫是小爬虫,但小事情多做几次,就会越发的精细,好了,也没啥讲的了,祝大家写程序没有bug,天天开心。

啊,对啦对啦,点个赞,点个关注吧,蟹蟹支持。

五、完整代码

import os
from urllib import parse
import requests
from docx import Document
from lxml import etree
import re
import random
from multiprocessing import Pool

import time


class biqugeCrawl():

    m_url = "http://www.biquge.tv/"

	 # 查询书籍函数
    def search_book(self):
        serach_url = "http://www.biquge.tv/modules/article/search.php?searchkey={}"

        name = input("请输入书名:")

        name = parse.quote(name.encode('gbk'))

        url = serach_url.format(name)

        response = requests.get(url=url, headers=self.__random_ua())

        root = etree.HTML(response.content)

        try:
            book_name = root.xpath('//*[@id="nr"]/td[1]/a/text()')[0]

            book_author = root.xpath('//*[@id="nr"]/td[3]/text()')[0]

            book_url = root.xpath('//*[@id="nr"]/td[1]/a/@href')[0]

            return [book_name, book_author, book_url]

        except:
            return None

    # 下载函数
    def download(self, book_name, url):
        response = requests.get(url=url, headers=self.__random_ua())
        root = etree.HTML(response.content)

        content_urls = list(map(lambda x: "http://www.biquge.tv/" + x, root.xpath('//*[@id="list"]/dl/dd/a/@href')[9:]))
        content_names = root.xpath('//*[@id="list"]/dl/dd/a/text()')[9:]

        # 创建以书名命名的文件夹
        path = '《' + book_name + '》'
        if not os.path.exists(path):
            os.mkdir(path)

        utls_names = [i for i in zip(content_urls, content_names)]

        pool = Pool(processes=5)
        pool.map(self.parse, utls_names)

        print("《{}》下载完毕!".format(book_name))


    # 爬取文章内容函数
    def parse(self, url_name):
        url = url_name[0]
        name = url_name[1]

        try:
            response = requests.get(url=url, headers=self.__random_ua())
            html = response.content.decode('gbk')
    
            root = etree.HTML(response.content)
            book_name = root.xpath('//div[@class="con_top"]/a[2]/text()')[0]
    
            content = "".join(re.findall('
(.*?)
'
, html, re.S)) content = re.sub("<.*?>", "", re.sub("  ", " ", content)) # 创建document对象 document = Document() # 将文本写入文档 document.add_paragraph(content) document.save("《{}》/《{}》.docx".format(book_name, name)) print('《' + name + '》已下载完成') except Exception as e: with open("./log.txt", "a+", encoding="utf-8") as file: file.write("*"*30+"\n"+str(e)) print("出现异常,下载中断,请查看log文件!") pass # 随机UA def __random_ua(self): UA = ["Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0", "Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot;Gecko/20100101 Firefox/38.0", "Mozilla/5.0 (Windows NT 5.1; rv:36.0) Gecko/20100101 Firefox/36.0", "Mozilla/5.0 (Windows NT 5.1; rv:33.0) Gecko/20100101 Firefox/33.0", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0", "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0", "Mozilla/5.0 (Windows NT 5.1; rv:40.0) Gecko/20100101 Firefox/40.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0", "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/20.6.14", "Mozilla/5.0 (Windows NT 5.1; rv:30.0) Gecko/20100101 Firefox/30.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0", "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0", "Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot; Gecko/20100101 Firefox/38.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0" ] headers = { "User-Agent": {} } headers["User-Agent"] = random.choice(UA) return headers if __name__ == '__main__': biquge = biqugeCrawl() print("----笔趣阁小说爬虫----") print("1-------------搜索小说") print("2----------------退出") flag = input("请输入指令选择相应功能:") while 1: error_str = "" if flag == "1": book = biquge.search_book() if book is not None: flag = input("搜索到的书名为《{}》,作者名为{},请确认是否下载【Y/N】".format(book[0], book[1])) while 1: if flag == 'Y' or flag == 'y': biquge.download(book[0], book[2]) break elif flag == 'N' or flag == 'n': break else: flag = input("请正确输入【Y/N】!") else: print("查无此书") elif flag == "2": exit(1) else: error_str = "指令错误," flag = input("{}请重新输入指令选择相应功能【1.搜索;2.退出】:".format(error_str))

你可能感兴趣的:(#,Python爬虫学习,笔趣阁爬虫,python,笔趣阁)