大师兄的Python学习笔记(二十): 爬虫(一)
大师兄的Python学习笔记(二十二): 爬虫(三)
三、提取信息
2. 使用XPATH
- 大师兄的Python学习笔记(十九): Python与(XML和JSON)
- 相比正则表达式,用选择器从网页定位资源更精准。
>>>import requests
>>>from lxml import etree
>>>def sort_data(func):
>>> def deco(*args,**kargs):
>>> # 处理内容
>>> data = func(*args,**kargs)
>>> html_data = etree.HTML(data) # 补全html文件
>>> index = html_data.xpath('//div[@class="pic"]/em[@class=""]//text()')
>>> link = html_data.xpath('//div[@class="hd"]/a/@href')
>>> name = html_data.xpath('//div[@class="hd"]/a/span[@class="title"][1]/text()')
>>> director_and_actor = [(x.strip()).split('\xa0\xa0\xa0') for x in html_data.xpath('//div[@class="bd"]/p[1]/text()[1]')]
>>> director = [x[0] for x in director_and_actor]
>>> actor = [x[1] for x in director_and_actor]
>>> inq = html_data.xpath('//div[@class="bd"]/div[@class="star"]/span[4]/text()')
>>>
>>> result = zip(index,name,director,actor,inq,link)
>>> return result
>>> return deco
>>>@sort_data
>>>def get_page(url):
>>> # 获得页面内容
>>> headers = {
>>> 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like >>>>Gecko) Chrome/78.0.3904.108 Safari/537.36'
>>> }
>>> res = requests.get(url=url,headers=headers)
>>> if res.status_code == 200:
>>> return res.text # 获得HTML页面
>>> else:
>>> return None
>>>def show_result(data):
>>> # 打印结果
>>> for i in range(10):
>>> print(next(data))
>>>def main():
>>> # 入口
>>> url = 'https://movie.douban.com/top250'
>>> page_data = get_page(url)
>>> show_result(page_data)
>>>if __name__ == '__main__':
>>> main()
('1', '肖申克的救赎', '导演: 弗兰克·德拉邦特 Frank Darabont', '主演: 蒂姆·罗宾斯 Tim Robbins /...', '2077511人评价', 'https://movie.douban.com/subject/1292052/')
('2', '霸王别姬', '导演: 陈凯歌 Kaige Chen', '主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha...', '1540213人评价', 'https://movie.douban.com/subject/1291546/')
('3', '阿甘正传', '导演: 罗伯特·泽米吉斯 Robert Zemeckis', '主演: 汤姆·汉克斯 Tom Hanks / ...', '1569821人评价', 'https://movie.douban.com/subject/1292720/')
('4', '这个杀手不太冷', '导演: 吕克·贝松 Luc Besson', '主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ...', '1760375人评价', 'https://movie.douban.com/subject/1295644/')
('5', '美丽人生', '导演: 罗伯托·贝尼尼 Roberto Benigni', '主演: 罗伯托·贝尼尼 Roberto Beni...', '983764人评价', 'https://movie.douban.com/subject/1292063/')
('6', '泰坦尼克号', '导演: 詹姆斯·卡梅隆 James Cameron', '主演: 莱昂纳多·迪卡普里奥 Leonardo...', '1522727人评价', 'https://movie.douban.com/subject/1292722/')
('7', '千与千寻', '导演: 宫崎骏 Hayao Miyazaki', '主演: 柊瑠美 Rumi Hîragi / 入野自由 Miy...', '1631425人评价', 'https://movie.douban.com/subject/1291561/')
('8', '辛德勒的名单', '导演: 史蒂文·斯皮尔伯格 Steven Spielberg', '主演: 连姆·尼森 Liam Neeson...', '799685人评价', 'https://movie.douban.com/subject/1295124/')
('9', '盗梦空间', '导演: 克里斯托弗·诺兰 Christopher Nolan', '主演: 莱昂纳多·迪卡普里奥 Le...', '1499201人评价', 'https://movie.douban.com/subject/3541415/')
('10', '忠犬八公的故事', '导演: 莱塞·霍尔斯道姆 Lasse Hallström', '主演: 理查·基尔 Richard Ger...', '1043142人评价', 'https://movie.douban.com/subject/3011091/')
3. 使用Beautiful Soup
3.1 关于bs4
- Beautiful Soup是一个的网页解析库,可以方便的从网页中提取数据。
- 由于我们目前通常使用的是Beautiful Soup第四版,所以安装时使用:
pip install beautifulsoup4 / conda install beautifulsoup4
。 - 引用时使用:
from bs4 import BeautifulSoup
>>>from bs4 import BeautifulSoup
>>>s = BeautifulSoup('<view>Hello World!</view>','html.parser')
>>>print(s.view.string)
Hello World!
3.2 解析器
- bs4支持多种解析器:
解析器 | 优点 | 缺点 |
---|---|---|
html.parser | Python标准库; 速度适中; 容错能力强 |
老版本Python容错能力差 |
lxml | 速度快; 容错能力强; |
需要安装第三方包 |
xml | 速度快; 唯一支持xml; |
需要安装第三方包 |
html5lib | 最强容错; 生成HTML5文档 |
速度慢 |
3.3 创建BeautifulSoup对象
- 使用
bs4.BeautifulSoup()
函数,将一段html字符串解析成BeautifulSoup对象。
>>>html = '''<html>
>>> <head>
>>> <title>
>>> wemud
>>> </title>
>>> </head>
>>> <body>
>>> <p class="title">
>>> wemud
>>> </p>
>>> <p class="content">
>>> play free online.
>>> </p>
>>> <a id="link1" href="www.wemud.net.cn">
>>> click to join
>>> </a>
>>> </body>
>>> </html>'''
>>>s = BeautifulSoup(html,'html.parser')
>>>print(type(s))
<class 'bs4.BeautifulSoup'>
3.4 内容选择
- 有了BeautifulSoup对象后,可以使用非常"Python"的方法选择内容。
3.4.1 节点选择器
1) 选择节点元素
- 通过调用节点名称就可以选择节点元素
- 默认第一个元素。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.p) <p class="title> wemud </p> <p class=" content"=""> play free online. </p>
2) 使用.name属性获得节点名称
- 默认第一个元素。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.p.name) p
3) 使用.attrs获得节点属性
- 返回的是一个属性dict。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.p.attrs) {'class': ['title']}
4) 使用.string获得文本内容
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.title.string) wemud
5) 嵌套选择
- 由于每个节点都是bs4.element.Tag对象,所以可以嵌套选择。
- 默认第一个元素。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.body.p.string) wemud
6) 关联选择
- 使用
.contents
获得直接子节点元素列表:>>>s = BeautifulSoup(html,'html.parser') >>>print(s.body.contents) ['\n', <p class="title"> wemud </p>, '\n', <p class="content"> play free online. </p>, '\n', <a href="www.wemud.net.cn" id="link1"> click to join </a>, '\n']
- 使用
.children
获得直接子节点元素的列表生成器:>>>s = BeautifulSoup(html,'html.parser') >>>for child in s.body.children: >>> print(child) <p class="title"> wemud </p> <p class="content"> play free online. </p> <a href="www.wemud.net.cn" id="link1"> click to join </a>
- 使用
.descendants
获得所有子节点元素的生成器:>>>s = BeautifulSoup(html,'html.parser') >>>for index,child in enumerate(s.html.descendants): >>> print(f'{index}:{child}') 0: 1:<head> <title> wemud </title> </head> 2: 3:<title> wemud </title> 4: wemud 5: 6: 7:<body> <p class="title"> wemud </p> <p class="content"> play free online. </p> <a href="www.wemud.net.cn" id="link1"> click to join </a> </body> 8: 9:<p class="title"> wemud </p> 10: wemud 11: 12:<p class="content"> play free online. </p> 13: play free online. 14: 15:<a href="www.wemud.net.cn" id="link1"> click to join </a> 16: click to join 17: 18:
- 使用
.parent
获得父节点元素:>>>s = BeautifulSoup(html,'html.parser') >>>print(s.p.parent.name) body
- 使用
.parents
获得所有祖先节点元素的生成器:>>>s = BeautifulSoup(html,'html.parser') >>>parents = s.title.parents >>>while True: >>> try: >>> print(next(parents).name) >>> except StopIteration as e: >>> break head html [document]
- 使用
.next_sibling
和.previous_sibling
获得兄弟节点元素:>>>s = BeautifulSoup(html,'html.parser') >>>>print((s.head.next_sibling).name) body >>>print((s.body.previous_sibling).name) head
3.4.2 方法选择器
- 方法选择器可以根据条件定位元素。
- 查询方法:
1) find(name,attrs,recursive,text,**kwargs)和find_all(name,attrs,recursive,text,**kwargs)
- 常用方法。
- 它们的区别是查找第一个元素或全部元素。
2) find_parent()和find_parents()
- 区别为查询父节点和所有祖先节点。
3) find_next_sibling()和find_next_siblings()
- 区别为查询下一个或所有兄弟节点。
4) find_previous_sibling()和find_previous_siblings()
- 区别为查询上一个或所有兄弟节点。
5) find_next()和find_all_next()
- 区别为查询节点后第一个或所有符合条件的节点。
6) find_previous()和find_all_previous()
- 区别为查询节点前第一个或所有符合条件的节点。
- 查询参数:
1) 节点名查询
- 使用name参数查询节点名。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.find_all(name="p")) [<p class="title"> wemud </p>, <p class="content"> play free online. </p>]
2) 节点属性查询
- 使用attrs参数传入属性-值的字典进行查询。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.find_all(attrs={"class":"title"})) [<p class="title"> wemud </p>]
- 常用属性可以直接作为参数进行查询。
- 如果属性名正好是Python关键字,则在后面加_,比如:
class_
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.find_all(class_="title")) [<p class="title"> wemud </p>]
3) 节点文本查询
- 使用text参数查询文本内容。
- 可以传入字符串或正则表达式。
>>>s = BeautifulSoup(html,'html.parser') >>>print(s.find_all(text=re.compile("wemud"))) ['\n wemud\n ', '\n wemud\n ']
3.4.3 CSS选择器
- CSS选择器是Web开发常用的工具。
- 使用
select(selector, namespaces=None, limit=None, **kwargs)
方法传入CSS选择器即可。
>>>s = BeautifulSoup(html,'html.parser')
>>>for title in s.select(".title"):
>>> print(title.get_text())
>>>for link1 in s.select("#link1"):
>>> print(link1.attrs)
wemud
{'id': 'link1', 'href': 'www.wemud.net.cn'}
3.5 使用bs4实现demo
>>>import requests,re
>>>from bs4 import BeautifulSoup
>>>def sort_data(func):
>>> def deco(*args,**kargs):
>>> # 处理内容
>>> data = func(*args,**kargs)
>>> html_data = BeautifulSoup(data)
>>> index = [x.string for x in html_data.find_all(name="em")]
>>> name = [x.find(class_="title").string for x in html_data.find_all(class_="hd")]
>>> director_actor = [x.strip() for x in html_data.find_all(text=re.compile("导演:(.*?)"))]
>>> director = [x.split('\xa0\xa0\xa0')[0] for x in director_actor]
>>> actor = [x.split('\xa0\xa0\xa0')[1] for x in director_actor]
>>> star = [x for x in html_data.find_all(text=re.compile('.*?人评价'))]
>>> link = [x.a.attrs['href'] for x in html_data.find_all(class_="hd")]
>>> result = zip(index,name,director,actor,star,link)
>>> return result
>>> return deco
>>>@sort_data
>>>def get_page(url):
>>> # 获得页面内容
>>> headers = {
>>> 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like >>>>Gecko) Chrome/78.0.3904.108 Safari/537.36'
>>> }
>>> res = requests.get(url=url,headers=headers)
>>> if res.status_code == 200:
>>> return res.text # 获得HTML页面
>>> else:
>>> return None
>>>def show_result(data):
>>> # 打印结果
>>> for i in range(10):
>>> print(next(data))
>>>def main():
>>> # 入口
>>> url = 'https://movie.douban.com/top250'
>>> page_data = get_page(url)
>>> show_result(page_data)
>>>if __name__ == '__main__':
>>> main()
('1', '肖申克的救赎', '导演: 弗兰克·德拉邦特 Frank Darabont', '主演: 蒂姆·罗宾斯 Tim Robbins /...', '2077511人评价', 'https://movie.douban.com/subject/1292052/')
('2', '霸王别姬', '导演: 陈凯歌 Kaige Chen', '主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha...', '1540213人评价', 'https://movie.douban.com/subject/1291546/')
('3', '阿甘正传', '导演: 罗伯特·泽米吉斯 Robert Zemeckis', '主演: 汤姆·汉克斯 Tom Hanks / ...', '1569821人评价', 'https://movie.douban.com/subject/1292720/')
('4', '这个杀手不太冷', '导演: 吕克·贝松 Luc Besson', '主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ...', '1760375人评价', 'https://movie.douban.com/subject/1295644/')
('5', '美丽人生', '导演: 罗伯托·贝尼尼 Roberto Benigni', '主演: 罗伯托·贝尼尼 Roberto Beni...', '983764人评价', 'https://movie.douban.com/subject/1292063/')
('6', '泰坦尼克号', '导演: 詹姆斯·卡梅隆 James Cameron', '主演: 莱昂纳多·迪卡普里奥 Leonardo...', '1522727人评价', 'https://movie.douban.com/subject/1292722/')
('7', '千与千寻', '导演: 宫崎骏 Hayao Miyazaki', '主演: 柊瑠美 Rumi Hîragi / 入野自由 Miy...', '1631425人评价', 'https://movie.douban.com/subject/1291561/')
('8', '辛德勒的名单', '导演: 史蒂文·斯皮尔伯格 Steven Spielberg', '主演: 连姆·尼森 Liam Neeson...', '799685人评价', 'https://movie.douban.com/subject/1295124/')
('9', '盗梦空间', '导演: 克里斯托弗·诺兰 Christopher Nolan', '主演: 莱昂纳多·迪卡普里奥 Le...', '1499201人评价', 'https://movie.douban.com/subject/3541415/')
('10', '忠犬八公的故事', '导演: 莱塞·霍尔斯道姆 Lasse Hallström', '主演: 理查·基尔 Richard Ger...', '1043142人评价', 'https://movie.douban.com/subject/3011091/')
4. 使用Pyquery
- Pyquery是Jquery的"Python版",语法和Jquery类似。
- 相比Beautiful Soup更灵活。
- 通常使用
from pyquery import PyQuery as pq
导入
4.1 初始化pyquery对象
- 与bs4类似,解析前需要先初始化对象。
- 使用
PyQuery()
方法初始化字符串对象。
>>>from pyquery import PyQuery as pq
>>>html = '''<html>
>>> <head>
>>> <title>
>>> wemud
>>> </title>
>>> </head>
>>> <body>
>>> <p class="title">
>>> wemud
>>> </p>
>>> <p class="content">
>>> play free online.
>>> </p>
>>> <a id="link1" href="www.wemud.net.cn">
>>> click to join
>>> </a>
>>> </body>
>>> </html>'''
>>>page = pq(html) # 初始化对象
>>>print(type(page))
<class 'pyquery.pyquery.PyQuery'>
- 也可以直接传入网页URL进行初始化对象。
>>>from pyquery import PyQuery as pq
>>>page = pq(url="http://www.baidu.com")
>>>print(type(page))
<class 'pyquery.pyquery.PyQuery'>
- 也可以直接从文件初始化。
>>>from pyquery import PyQuery as pq
page = pq(filename="test.xml")
print(type(page))
<class 'pyquery.pyquery.PyQuery'>
4.2 CSS选择器
4.2.1 基本用法
- 与前端的CSS选择器语法类似。
>>>page = pq(html)
>>>print(page('body .title'))
<p class="title">
wemud
</p>
>>>print(page('#link1'))
<a id="link1" href="www.wemud.net.cn">
click to join
</a>
4.2.2 查询方法
1) find()函数
- 查询所有符合条件的子节点。
>>>page = pq(html) >>>body = page("body") >>>print(body.find('p')) <p class="title"> wemud </p> <p class="content"> play free online. </p>
2) parent()函数
- 获得目标的父节点。
>>>page = pq(html) >>>title = page("title") >>>print(title.parent()) <head> <title> wemud </title> </head>
3) parents()函数
- 获得所有符合条件的祖先节点。
>>>page = pq(html) >>>title = page("title") >>>print(title.parents('head')) <head> <title> wemud </title> </head>
4) siblings()函数
- 获得所有符合条件的兄弟节点。
>>>page = pq(html) >>>a = page("#link1") >>>print(a.siblings('.title')) <p class="title"> wemud </p>
5) items()函数
- 用于获取返回结果的生成器。
>>>page = pq(html) >>>p = page("p") >>>print(type(p)) <class 'pyquery.pyquery.PyQuery'> >>>print(type(p.items())) <class 'generator'>
4.2.3 获取内容
1) 获取属性
- 使用
attr()
函数,或.attr
属性。>>>page = pq(html) >>>a = page("#link1") >>>print(a.attr('href')) www.wemud.net.cn >>>print(a.attr.href) www.wemud.net.cn
2) 获取文本
- 使用text()函数获得目标的纯文本信息。
- 使用html()函数获得目标的html文本信息。
>>>page = pq(html) >>>body = page("body") >>>print(body.text()) wemud play free online. click to join >>>print(body.html()) <p class="title"> wemud </p> <p class="content"> play free online. </p> <a id="link1" href="www.wemud.net.cn"> click to join </a>
4.2.4 节点动态操作
- pyquery支持对节点进行动态修改,常用方法如下:
1) 变更class属性
- 使用
addClass()
方法增加。>>>page = pq(html) >>>body = page("body") >>>body.addClass("body") >>>print(body.attr.class_) body
- 使用
removeClass()
方法删除。>>>page = pq(html) >>>p = page(".title") >>>p.removeClass("title") >>>print(p) <p class=""> wemud </p>
2) 变更节点属性
- 使用
attr()
方法修改属性。>>>page = pq(html) >>>p = page(".content") >>>p.attr("class","new class") >>>p.attr("id","new id") >>>print(p) <p class="new class" id="new id"> play free online. </p>
- 使用
text()
方法修改纯文本。>>>page = pq(html) >>>p = page(".content") >>>p.text("some new content") >>>print(p) <p class="content">some new content</p>
- 使用
html()
方法修改html文本。>>>page = pq(html) >>>p = page(".content") >>>p.html("<text>some new content</text>") >>>print(p) <p class="content"><text>some new content</text></p>
3) 变更节点
- 使用
append()
方法增加节点。>>>page = pq(html) >>>body = page("body") >>>body.append("<p>i'm new here</p>") >>>print(body) <body> <p class="title"> wemud </p> <p class="content"> play free online. </p> <a id="link1" href="www.wemud.net.cn"> click to join </a> <p>i'm new here</p></body>
- 使用
remove()
方法删除节点。>>>page = pq(html) >>>p = page(".content") >>>p.remove() >>>print(page("body")) <body> <p class="title"> wemud </p> <a id="link1" href="www.wemud.net.cn"> click to join </a> </body>
4.2.5 伪类选择器
- pyquery支持各种伪类选择器。
- 全部伪类选择器
>>>page = pq(html)
>>>print(page('.title:first-child'))
<p class="title">
wemud
</p>
4.3 使用pyquery实现demo
>>>from pyquery import PyQuery as pq
>>>import requests
>>>def sort_data(func):
>>> def deco(*args,**kargs):
>>> # 处理内容
>>> data = func(*args,**kargs)
>>> html_data = pq(data)
>>> hd = html_data('.hd')
>>> bd = html_data('.bd')
>>> index = [x.text() for x in html_data.find('em').items()]
>>> name = [x.text() for x in hd.find('.title:first-child').items()]
>>> director_actor = [x.html().strip().split('<br/>')[0] for x in bd.children('.star').siblings('p:first-of-type').items()]
>>> director = [x.split('\xa0\xa0\xa0')[0] for x in director_actor]
>>> actor = [x.split('\xa0\xa0\xa0')[1] for x in director_actor]
>>> star = bd('.star')('span:nth-child(4)').text().split()
>>> link = [x.attr.href for x in hd('a').items()]
>>> result = zip(index,name,director,actor,star,link)
>>> return result
>>> return deco
>>>@sort_data
>>>def get_page(url):
>>> # 获得页面内容
>>> headers = {
>>> 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like >>>>Gecko) Chrome/78.0.3904.108 Safari/537.36'
>>> }
>>> res = requests.get(url=url,headers=headers)
>>> if res.status_code == 200:
>>> return res.text # 获得HTML页面
>>> else:
>>> return None
>>>def show_result(data):
>>> # 打印结果
>>> for i in range(10):
>>> print(next(data))
>>>def main():
>>> # 入口
>>> url = 'https://movie.douban.com/top250'
>>> page_data = get_page(url)
>>> show_result(page_data)
>>>if __name__ == '__main__':
>>> main()
('1', '肖申克的救赎', '导演: 弗兰克·德拉邦特 Frank Darabont', '主演: 蒂姆·罗宾斯 Tim Robbins /...', '2077511人评价', 'https://movie.douban.com/subject/1292052/')
('2', '霸王别姬', '导演: 陈凯歌 Kaige Chen', '主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha...', '1540213人评价', 'https://movie.douban.com/subject/1291546/')
('3', '阿甘正传', '导演: 罗伯特·泽米吉斯 Robert Zemeckis', '主演: 汤姆·汉克斯 Tom Hanks / ...', '1569821人评价', 'https://movie.douban.com/subject/1292720/')
('4', '这个杀手不太冷', '导演: 吕克·贝松 Luc Besson', '主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ...', '1760375人评价', 'https://movie.douban.com/subject/1295644/')
('5', '美丽人生', '导演: 罗伯托·贝尼尼 Roberto Benigni', '主演: 罗伯托·贝尼尼 Roberto Beni...', '983764人评价', 'https://movie.douban.com/subject/1292063/')
('6', '泰坦尼克号', '导演: 詹姆斯·卡梅隆 James Cameron', '主演: 莱昂纳多·迪卡普里奥 Leonardo...', '1522727人评价', 'https://movie.douban.com/subject/1292722/')
('7', '千与千寻', '导演: 宫崎骏 Hayao Miyazaki', '主演: 柊瑠美 Rumi Hîragi / 入野自由 Miy...', '1631425人评价', 'https://movie.douban.com/subject/1291561/')
('8', '辛德勒的名单', '导演: 史蒂文·斯皮尔伯格 Steven Spielberg', '主演: 连姆·尼森 Liam Neeson...', '799685人评价', 'https://movie.douban.com/subject/1295124/')
('9', '盗梦空间', '导演: 克里斯托弗·诺兰 Christopher Nolan', '主演: 莱昂纳多·迪卡普里奥 Le...', '1499201人评价', 'https://movie.douban.com/subject/3541415/')
('10', '忠犬八公的故事', '导演: 莱塞·霍尔斯道姆 Lasse Hallström', '主演: 理查·基尔 Richard Ger...', '1043142人评价', 'https://movie.douban.com/subject/3011091/')
参考资料
- https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
- https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
- https://www.runoob.com 菜鸟教程
- http://www.tulingxueyuan.com/ 北京图灵学院
- http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
- https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
- https://realpython.com/python-string-formatting/ Dan Bader
- https://www.liaoxuefeng.com/ 廖雪峰
- https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
- https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
- https://www.cnblogs.com/dalaoban/p/9331113.html 超天大圣
- https://blog.csdn.net/zhubao124/article/details/81662775 zhubao124
- https://blog.csdn.net/z59d8m6e40/article/details/72871485 z59d8m6e40
- 《Python学习手册》Mark Lutz
- 《Python编程 从入门到实践》Eric Matthes
本文作者:大师兄(superkmi)