之前写的笔趣阁爬虫有不少同学说不能爬了,我后来发现是网站改版的缘故,前些日子把书里的爬虫项目都整的差不多了,现在又有些不知道爬什么好了,刚好在这段时间把笔趣阁爬虫代码重写一下。
(PS:我看自己之前写的代码感觉写的好丑哦,官方吐槽,最为致命,以前的链接)
做了以下改进:
前期准备,需要在cmd里下载安装以下第三方库:
pip install requests
pip install python-docx
以下是爬取笔趣阁小说的主体思路,爬虫代码在这个思路上扩展构建。
笔趣阁的网站众多,这是我爬取的笔趣阁网址:http://www.biquge.tv/
我将爬虫写到 biqugeCrawl 类中。
以搜索圣墟为例,网站的搜索是靠这个searchkey这个参数。在biqugeCrawl类中定义一个search_book() 函数专用于查找小说功能。
1.1、 大家看到这个%什么的或许会很陌生,这是因为URL 只允许一部分 ASCII 字符(数字字母和部分符号),其他的字符(如汉字)是不符合 URL 标准的,所以我们这里使用 urllib库 的 parse.quote() 将其转换为URL编码。
转换后还是不符合要求,怀疑是编码格式的问题,去网页的源码中查看,如下显示为GBK编码,所以我们输入的书名还要先转换为GBK格式。
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("查无此书")
上一步写得search_book() 函数在搜到小说得情况下返回了三个数据:小说名、作者名、小说得url,经由用户确认,调用biqugeCrawl.download() ,获取小说章节链接,并采用多线程下载内容。
2.1、 请求网页,获取链接,因为它有九个最新章节得在里面,所以我们使用xpath解析出来得列表要略去这九个,直接用切片切掉。
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)
上面一步是获取到了小说章节的目录,并采用多进程的方法调用了biqugeCrawl.parse() 函数,这个函数的作用就是下载并保存小说内容。
3.1、 解析出小说的主体内容,观察网页源码,可以发现,小说的内容都在html标签中,我用正则表达式将其解析出来,并对文本做了一些处理。
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数据存到数据库中,上手简单,建议学习搭建。
这爬取笔趣阁本来就是个小爬虫,我感觉很适合让刚入爬虫的同学学习的,虽然相比那种只爬取单个网页的入门学习方式要多些困难,不过克服困难后还是会有很大的进步的,这是我整理的【爬虫实战项目集合】,有兴趣的可以看看,由浅入深的学习爬虫,最简单的和较为复杂的都有。
对于这个爬笔趣阁的项目,我认为有一个很大的槽点,它是多进程爬取出来的,文件按时间排序不行,按名称排序又是下面这样,emm,太难看了,还是之前老版本的非多进程好看,但就是慢些,没有思路解决这个问题,求教。
另外,我想在爬虫里加上暂定下载的功能,以及断网处理的功能(这两个功能是一个原理,搞一个任务队列),这爬取笔趣阁小爬虫是小爬虫,但小事情多做几次,就会越发的精细,好了,也没啥讲的了,祝大家写程序没有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))