专栏名称: Python学习交流
每天更新,更新python相关的知识。希望诸君有所收获!
目录
相关文章推荐
Python爱好者社区  ·  史上最强!PINN杀疯了 ·  昨天  
Python爱好者社区  ·  DeepSeek创始人梁文锋个人履历 ·  3 天前  
Python爱好者社区  ·  1885页的Python完全版电子书 ·  3 天前  
Python开发者  ·  o3-mini 碾压 DeepSeek ... ·  5 天前  
Python开发者  ·  请立即拿下软考证书(政策风口) ·  4 天前  
51好读  ›  专栏  ›  Python学习交流

Python下载某网站全站小说!什么?还有付费的?爬虫面前没付费!

Python学习交流  · 公众号  · Python  · 2018-12-07 19:17

正文



小试牛刀

我们先看下《一念永恒》小说的第一章内容,URL:

http://www.biqukan.com/1_1094/5403177.html



我们先用已经学到的知识获取HTML信息试一试,编写代码如下:


# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url=target)
print(req.text)


运行代码,可以看到如下结果:



可以看到,我们很轻松地获取了HTML信息。但是,很显然,很多信息是我们不想看到的,我们只想获得如右侧所示的正文内容,我们不关心div、br这些html标签。 如何把正文内容从这些众多的html标签中提取出来呢?这就是本次实战的主要内容。

私信小编007即可获取一个神秘的大礼包哦!


一个强大的第三方库,都会有一个详细的官方文档。我们很幸运,Beautiful Soup也是有中文的官方文档。URL:

http://beautifulsoup.readthedocs.io/zh_CN/latest/

同理,我会根据实战需求,讲解Beautiful Soup库的部分使用方法,更详细的内容,请查看官方文档。

现在,我们使用已经掌握的审查元素方法,查看一下我们的目标页面,你会看到如下内容:



不难发现,文章的所有内容都放在了一个名为div的“东西下面”,这个"东西"就是html标签。HTML标签是HTML语言中最基本的单位,HTML标签是HTML最重要的组成部分。不理解,没关系,我们再举个简单的例子:

一个女人的包包里,会有很多东西,她们会根据自己的习惯将自己的东西进行分类放好。镜子和口红这些会经常用到的东西,会归放到容易拿到的外侧口袋里。那些不经常用到,需要注意安全存放的证件会放到不容易拿到的里侧口袋里。


html标签就像一个个“口袋”,每个“口袋”都有自己的特定功能,负责存放不同的内容。显然,上述例子中的div标签下存放了我们关心的正文内容。这个div标签是这样的:


细心的朋友可能已经发现,除了div字样外,还有id和class。id和class就是div标签的属性,content和showtxt是属性值,一个属性对应一个属性值。这东西有什么用?它是用来区分不同的div标签的,因为div标签可以有很多,我们怎么加以区分不同的div标签呢?就是通过不同的属性值。

仔细观察目标网站一番,我们会发现这样一个事实: class属性为showtxt的div标签,独一份!这个标签里面存放的内容,是我们关心的正文部分。


知道这个信息,我们就可以使用Beautiful Soup提取我们想要的内容了,编写代码如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
print(texts)

在解析html之前,我们需要创建一个Beautiful Soup对象。BeautifulSoup函数里的参数就是我们已经获得的html信息。然后我们使用find_all方法,获得html信息中所有class属性为showtxt的div标签。find_all方法的第一个参数是获取的标签名,第二个参数class_是标签的属性,为什么不是class,而带了一个下划线呢?因为python中class是关键字,为了防止冲突,这里使用class_表示标签的class属性,class_后面跟着的showtxt就是属性值了。看下我们要匹配的标签格式:


这样对应的看一下,是不是就懂了?可能有人会问了,为什么不是find_all('div', id = 'content', class_ = 'showtxt')?这样其实也是可以的,属性是作为查询时候的约束条件,添加一个class_='showtxt'条件,我们就已经能够准确匹配到我们想要的标签了,所以我们就不必再添加id这个属性了。运行代码查看我们匹配的结果:



我们可以看到,我们已经顺利匹配到我们关心的正文内容,但是还有一些我们不想要的东西。比如div标签名,br标签,以及各种空格。怎么去除这些东西呢?我们继续编写代码:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/5403177.html'
req = requests.get(url = target) html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
print(texts[0].text.replace('\xa0'*8,' '))

find_all匹配的返回的结果是一个列表。提取匹配结果后,使用text属性,提取文本内容,滤除br标签。随后使用replace方法,剔除空格,替换为回车进行分段。 在html中是用来表示空格的。replace('\xa0'*8,' ')就是去掉下图的八个空格符号,并用回车代替:




程序运行结果如下:



可以看到,我们很自然的匹配到了所有正文内容,并进行了分段。我们已经顺利获得了一个章节的内容,要想下载正本小说,我们就要获取每个章节的链接。我们先分析下小说目录:

URL:http://www.biqukan.com/1_1094/



通过审查元素,我们发现可以发现,这些章节都存放在了class属性为listmain的div标签下,选取部分html代码如下:



在分析之前,让我们先介绍一个概念:父节点、子节点、孙节点。

限定了
标签的开始和结束的位置,他们是成对出现的,有开始位置,就有结束位置。我们可以看到,在
标签包含
标签,那这个
标签就是
标签的子节点,
标签又包含
标签和
标签,那么
标签和
标签就是
标签的孙节点。有点绕?那你记住这句话: 谁包含谁,谁就是谁儿子!

他们之间的关系都是相对的 。比如对于

标签,它的子节点是 标签,它的父节点是
标签。这跟我们人是一样的,上有老下有小。

看到这里可能有人会问,这有好多

标签和 标签啊!不同的
标签,它们是什么关系啊?显然,兄弟姐妹喽!我们称它们为兄弟结点。

好了,概念明确清楚,接下来,让我们分析一下问题。我们看到每个章节的名字存放在了 标签里面。 标签还有一个href属性。这里就不得不提一下 标签的定义了, 标签定义了一个超链接,用于从一张页面链接到另一张页面。 标签最重要的属性是 href 属性,它指示链接的目标。

我们将之前获得的第一章节的URL和 标签对比看一下:

http://www.biqukan.com/1_1094/5403177.html 第一章 他叫白小纯


不难发现, 标签中href属性存放的属性值/1_1094/5403177.html是章节URLhttp://www.biqukan.com/1_1094/5403177.html的后半部分。其他章节也是如此!那这样,我们就可以根据 标签的href属性值获得每个章节的链接和名称了。

总结一下:小说每章的链接放在了class属性为listmain的

标签下的 标签中。链接具体位置放在html->body->div->dl->dd->a的href属性中。先匹配class属性为listmain的
标签,再匹配 标签。编写代码如下:
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
print(div[0])

还是使用find_all方法,运行结果如下:



很顺利,接下来再匹配每一个 标签,并提取章节名和章节文章。如果我们使用Beautiful Soup匹配到了下面这个 标签,如何提取它的href属性和 标签里存放的章节名呢?

第一章 他叫白小纯


方法很简单,对Beautiful Soup返回的匹配结果a,使用a.get('href')方法就能获取href的属性值,使用a.string就能获取章节名,编写代码如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
server = 'http://www.biqukan.com/'
target = 'http://www.biqukan.com/1_1094/'
req = requests.get(url = target) html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
for each in a:
print(each.string, server + each.get('href'))

因为find_all返回的是一个列表,里边存放了很多的 标签,所以使用for循环遍历每个 标签并打印出来,运行结果如下。



最上面匹配的一千多章的内容是最新更新的12章节的链接。这12章内容会和下面的重复,所以我们要滤除,除此之外,还有那3个外传,我们也不想要。这些都简单地剔除就好。

整合代码

每个章节的链接、章节名、章节内容都有了。接下来就是整合代码,将获得内容写入文本文件存储就好了。编写代码如下:

# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests, sys
"""
类说明:下载《笔趣看》网小说《一念永恒》
Parameters:

Returns:

Modify:
2017-09-13
"""
class downloader(object):
def __init__(self):
self.server = 'http://www.biqukan.com/'
self.target = 'http://www.biqukan.com/1_1094/'
self.names = [] #存放章节名
self.urls = [] #存放章节链接
self.nums = 0 #章节数
"""
函数说明:获取下载链接
Parameters:

Returns:

Modify:
2017-09-13
"""
def get_download_url(self):
req = requests.get(url = self.target)
html = req.text
div_bf = BeautifulSoup(html)
div = div_bf.find_all('div', class_ = 'listmain')
a_bf = BeautifulSoup(str(div[0]))
a = a_bf.find_all('a')
self.nums = len(a[15:]) #剔除不必要的章节,并统计章节数
for each in a[15:]:
self.names.append(each.string)
self.urls.append(self.server + each.get('href'))
"""
函数说明:获取章节内容
Parameters:
target - 下载连接(string)
Returns:
texts - 章节内容(string)
Modify:
2017-09-13
"""
def get_contents(self, target):
req = requests.get(url = target)
html = req.text
bf = BeautifulSoup(html)
texts = bf.find_all('div', class_ = 'showtxt')
texts = texts[0].text.replace('\xa0'*8,' ')
return texts
"""
函数说明:将爬取的文章内容写入文件
Parameters:
name - 章节名称(string)
path - 当前路径下,小说保存名称(string)
text - 章节内容(string)
Returns:

Modify:
2017-09-13
"""
def writer(self, name, path, text):
write_flag = True
with open(path, 'a', encoding='utf-8') as f:
f.write(name + ' ')
f.writelines(text)
f.write(' ')
if __name__ == "__main__":
dl = downloader()
dl.get_download_url()
print('《一年永恒》开始下载:')
for i in range(dl.nums):
dl.writer(dl.names[i], '一念永恒.txt', dl.get_contents(dl.urls[i]))
sys.stdout.write(" 已下载:%.3f%%" % float(i/dl.nums*100) + ' ')
sys.stdout.flush()
print('《一年永恒》下载完成')


很简单的程序,单进程跑,没有开进程池。下载速度略慢,喝杯茶休息休息吧。代码运行效果如下图所示:


'; mydiv.className = "img_loading"; mydiv.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="; videoPlaceHolderSpan.style.cssText = "width: " + obj.w + "px !important;"; mydiv.style.cssText += ";width: " + obj.w + "px"; videoPlaceHolderSpan.appendChild(videoPlayerIconSpan); videoPlaceHolderSpan.appendChild(mydiv); insertAfter(videoPlaceHolderSpan, a); a.style.cssText += ";width: " + obj.w + "px !important;"; a.setAttribute("width",obj.w); if(window.__zoom!=1){ a.style.display = "block"; videoPlaceHolderSpan.style.display = "none"; a.setAttribute("_ratio",obj.ratio); a.setAttribute("_vid",vid); }else{ videoPlaceHolderSpan.style.cssText += "height: " + obj.h + "px !important;"; mydiv.style.cssText += "height: " + obj.h + "px !important;"; a.style.cssText += "height: " + obj.h + "px !important;"; a.setAttribute("height",obj.h); } a.setAttribute("data-vh",obj.vh); a.setAttribute("data-vw",obj.vw); if(a.getAttribute("data-mpvid")){ a.setAttribute("data-src",location.protocol+"//mp.weixin.qq.com/mp/readtemplate?t=pages/video_player_tmpl&auto=0&vid="+vid); }else{ a.setAttribute("data-src",location.protocol+"//v.qq.com/iframe/player.html?vid="+ vid + "&width="+obj.vw+"&height="+obj.vh+"&auto=0"); } } })(); (function(){ if(window.__zoom!=1){ if (!window.__second_open__) { document.getElementById('page-content').style.zoom = window.__zoom; var a = document.getElementById('activity-name'); var b = document.getElementById('meta_content'); if(!!a){ a.style.zoom = 1/window.__zoom; } if(!!b){ b.style.zoom = 1/window.__zoom; } } var images = document.getElementsByTagName('img'); for (var i = 0,il=images.length;i=0 && child.getAttribute("data-vid")==vid){ child.style.cssText += "height: " + h + "px !important;"; child.style.display = ""; } } } } })(); })();






请到「今天看啥」查看全文