20194302实验四 Python综合实践
(1)实战背景
小说网站-笔趣看:
URL:http://www.biqukan.com/
笔趣看是一个盗版小说网站,这里有很多起点中文网的小说,该网站小说的更新速度稍滞后于起点中文网正版小说的更新速度。并且该网站只支持在线浏览,不支持小说打包下载。因此,本次实战就是从该网站爬取并保存一本名为《一念永恒》的小说,该小说是耳根已完结的一部玄幻小说。PS:本实验仅为交流学习,支持耳根大大,请上起点中文网订阅。
(2)初次尝试
我们先看下《一念永恒》小说的第一章内容,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 标签中提取出来呢?
(3)Beautiful Soup
爬虫的第一步,获取整个网页的 HTML 信息,我们已经完成。接下来就是爬虫的第二步,解析 HTML 信息,提取我们感兴趣的内容。对于这次实验,我们感兴趣的内容就是文章的正文。提取的方法有很多,例如使用正则表达式、Xpath、Beautiful Soup 等。这里使用简单的方法就是使用 Beautiful Soup 提取感兴趣内容。
Beautiful Soup 的安装方法和 requests 一样,使用如下指令安装:
1.pip installbeautifulsoup4
2.easy_installbeautifulsoup4
现在,我们使用已经掌握的审查元素方法,查看一下我们的目标页面,你会看到如下内容:
不难发现,文章的所有内容都放在了一个名为 div 的“东西下面”,这个"东西"就是 html 标签。HTML标签是 HTML 语言中最基本的单位,HTML 标签是 HTML 最重要的组成部分。
接下来,使用 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_是标签的属性。因为 python 中 class 是关键字,为了防止冲突,这里使用class_表示标签的 class 属性,class_后面跟着的 showtxt 就是属性值了。看下我们要匹配的标签格式:
<div id="content", class="showtxt">
运行代码查看我们匹配的结果:
我们可以看到,我们已经顺利匹配到我们关心的正文内容,但是还有一些我们不想要的东西。比如 div 标签名,br 标签,以及各种空格。怎么去除这些东西呢?我们继续编写代码:
# -*- coding:UTF-8 -*-
from bs4 importBeautifulSoup
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,‘\n\n‘))
find_all 匹配的返回的结果是一个列表。提取匹配结果后,使用 text 属性,提取文本内容,滤除 br 标签。随后使用 replace 方法,剔除空格,替换为回车进行分段。 在 html 中是用来表示空格的。replace(‘\xa0‘*8,‘\n\n‘)就是去掉下图的八个空格符号,并用回车代替:
程序运行结果如下:
可以看到,我们很自然的匹配到了所有正文内容,并进行了分段。我们已经顺利获得了一个章节的内容,要想下载整本小说,我们就要获取每个章节的链接。我们先分析下小说目录:URL:http://www.biqukan.com/1_1094/
通过审查元素,我们发现可以发现,这些章节都存放在了class 属性为 listmain 的 div 标签下,选取部分 html 代码如下:
<divclass="listmain">
<dl>
<dt>《一念永恒》最新章节列表</dt>
<dd><ahref="/1_1094/15932394.html">第 1027 章 第十道门</a></dd>
<dd><ahref="/1_1094/15923072.html">第 1026 章 绝伦道法!</a></dd>
<dd><ahref="/1_1094/15921862.html">第 1025 章 长生灯!</a></dd>
<dd><ahref="/1_1094/15918591.html">第 1024 章 一目晶渊</a></dd>
<dd><ahref="/1_1094/15906236.html">第 1023 章 通天道门</a></dd>
<dd><a href="/1_1094/15903775.html">第1022 章 四大凶兽!</a></dd>
<dd><ahref="/1_1094/15890427.html">第 1021 章 鳄首!</a></dd>
<dd><ahref="/1_1094/15886627.html">第 1020 章 一触即发!</a></dd>
<dd><ahref="/1_1094/15875306.html">第 1019 章 魁祖的气息!</a></dd>
<dd><ahref="/1_1094/15871572.html">第 1018 章 绝望的魁皇城</a></dd>
<dd><ahref="/1_1094/15859514.html">第 1017 章 我还是恨你!</a></dd>
<dd><ahref="/1_1094/15856137.html">第 1016 章 从来没有世界之门!</a></dd>
<dt>《一念永恒》正文卷</dt> <dd><ahref="/1_1094/5386269.html">外传 1 柯父。</a></dd>
<dd><ahref="/1_1094/5386270.html">外传 2 楚玉嫣。</a></dd> <dd><ahref="/1_1094/5386271.html">外传 3 鹦鹉与皮冻。</a></dd>
<dd><ahref="/1_1094/5403177.html">第一章 他叫白小纯</a></dd><dd><a href="/1_1094/5428081.html">第二章 火灶房</a></dd>
<dd><ahref="/1_1094/5433843.html">第三章 六句真言</a></dd><dd><a href="/1_1094/5447905.html">第四章 炼灵</a></dd>
</dl>
</div>
总结一下:小说每章的链接放在了 class 属性为 listmain 的<div>标签下的<a>标签中。链接具体位置放在html->body->div->dl->dd->a 的 href 属性中。先匹配 class 属性为 listmain 的<div>标签,再匹配<a>标签。编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 importBeautifulSoup
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 方法,运行结果如下:
接下来再匹配每一个<a>标签,并提取章节名和章节文章。
对 Beautiful Soup 返回的匹配结果 a,使用 a.get(‘href‘)方法就能获取 href 的属性值,使用 a.string 就能获取章节名,编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 importBeautifulSoup
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 返回的是一个列表,里边存放了很多的<a>标签,所以使用 for 循环遍历每个<a>标签并打印出来,运行结果如下:
最上面匹配的一千多章的内容是最新更新的 12 章节的链接。这 12 章内容会和下面的重复,所以我们要滤除。实验成功。
(4)整合代码
每个章节的链接、章节名、章节内容都有了。接下来就是整合代码,将获得内容写入文本文件存储就好了。编写代码如下:
# -*- coding:UTF-8 -*-
from bs4 importBeautifulSoup
import requests, sys
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 #章节数
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)
"""
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,‘\n\n‘)
return texts
"""
函数说明:将爬取的文章内容写入文件
Parameters:
name - 章节名称(string)
path - 当前路径下,小说保存名称(string)
text - 章节内容(string)
Returns:
无
"""
def writer(self, name, path, text):
write_flag = True
with open(path, ‘a‘, encoding=‘utf-8‘)as f:
f.write(name + ‘\n‘)
f.writelines(text)
f.write(‘\n\n‘)
if __name__ =="__main__":
dl = downloader()
dl.get_download_url()
print(‘《一年永恒》开始下载:‘)
for i in range(dl.nums):
dl.writer(dl.names[i], ‘一念永恒.txt‘,l.get_contents(dl.urls[i]))
sys.stdout.write(" 已下载:%.3f%%" % float(i/dl.nums*100) + ‘\r‘)
sys.stdout.flush()
print(‘《一年永恒》下载完成‘)
(5)学习感悟
学习Python也有一个学期了,感触最深的就是,学习编程不是一日之功,需要每天投入时间学习,也不可纸上谈兵,需要自己亲自操作,不动手就不会发现问题,动了手印象才会深刻,记得更靠。
接下来我就稍微总结一下,我在学习中遇到的问题和一些心得体会。
1.修改字符串大小写时常用的方法有upper,lower,局限性就是真个字符串都会变化,还有一种叫title的方法,可以只把首字母大写,非常好用。
2.列表本身是有顺序的,而且非常非常非常重要的一点,列表中的第一个数的顺序是“0”,索引是从0开始的!
3.修改列表时可以用,insert(位置,内容)来修改。
4.删除列表的内容时,用del可以直接锁定列表的位置来删除,用remove(内容),会将列表了第一次出现的内容删除,pop方法是直接删除末尾的内容,也可以看做是一种变相的提取,加上位置,比如pop(位置),就可以提取任何一个位置上的内容。
5.学习时还遇到了几个非常常用的方法,可能在课程中很少提到。len()来确定字符串的长度。range(数字),来生成从0开始一直到输入数字的一组连续的数字,例如输入的是10,会生成0到9的10个数。range(len()),这两者的结合可以很好的用来当做一组数据的索引,来确定数据中的位置,非常好用。
6.在写for循环时,要非常注意首行缩进,一般是缩进4个空格字符串,因为当嵌套的循环过多时,非常容易出错,系统无法识别出谁是谁的循环体。for我建议理解为遍历,就是指从头到尾看完整个数据。
7.while才是真正名义上的循环,只要满足条件就会重复进行,因此用while时一定要保证有可以结束循环的语句,比如说,用continue,break等,要不然系统就会无限进行循环。
8.range的左闭右开特性,举例子,range(1,5),以为是生成1到5折五个数字,但是根据python左闭右开的特性,实际是生成1到4这四个数字,切记,在切片,索引中非常重要。
9.切片就是根据位置来截取字符串的一部分,也是左闭右开。一些特殊的切片,[:]是取整个部分,[1:]是从位置1到结束,[:-1],负数代表从末尾数。
10.最开始我一直没有明白,列表,元祖,集合之间的区别,我简单描述一下,列表的限制要求最低,基本适用于任何一种情况,元祖不同,是不能修改元祖内的元素,因此元祖不可变,集合无序且不会重复。
11.if-elif-else, elif就可以理解为与if并列的条件判断,比如说,if我早饭吃的苹果,就会怎么样,elif我早饭吃的煎饼,就会怎么样,elif我早饭吃的面包,就会怎么样。三者并列判断
12.del可删除key-value对。
13.遍历字典用for key,value in 字典:
14.调用函数时,要给形式参数赋予一个实际参数,才会实现函数的内容,写实际参数时,顺序位置不能乱,与形式参数的位置对齐,当然如果在调用函数时已经带着形式参数一起写,就没顺序上的问题了。
15.列表的传递。可以先用pop方法,将一个列表内的数据储存在一个中间空列表内,然后再用append将中间列表里的数据,转移到最终想要的列表内,这样既实现了列表的转移。
16.导入模块后,如果调用了模块内的函数或者方法,一定要在前面制定模块名。
这些大概是我觉得比较重要的部分,当然,肯定还有很多遗漏的地方,也有很多还没遇到的难点知识点,学海无涯苦作舟,学习永无止境。我希望在未来能够学到更多对自己有用的知识,充实自己,提升自己。