python爬虫系统学习笔记

在中国大学MOOC那发现了python的爬虫专题系列,可以说是非常非常开心了

Requests库入门

​ 在前两次的爬虫体验里面已经用到了这个牛X的库,现在开始对它进行系统点的学习(emmm,个人不是很喜欢看文档,喜欢视频),嵩天老师讲的超级好,之前python入门也看过一点他的视频,感觉超级赞。废话貌似说的有点多。。。。

Requests库安装及七个基本方法

​ python提供了安装包管理工具pip,有点类似于Sublime text的Package Control,pip安装的时候注意版本的兼容,建议选择源码编译安装,解压的个人觉得容易出问题,具体请自行百度。有了pip以后就简单了,打开cmd直接输入键入命令pip install requests(此处针对的是windows环境,mac什么的应该更加方便就不说了)。然后检查一下是否已经成功安装。

1
2
3
4
import requests
r = requests.get("http://www.baidu.com")
print(r.status_code)
print(r.text)

这里我用的是Sublime Text,其实用python自带的idle也挺方便的,但我个人不习惯呃。

如果打印出状态码为200而且输出的百度的文本内容,说明已经requests库已经到位,否则请回去检查一下安装是否出了问题。

其中requests.request()是基础,构造请求。然后其他6个分别是:

get:获取HTML网页的主要方法

head: 获取HTML头部信息。

这是最为重要的两个,还有post, put, patch, delete。这6个方法均对应着HTTP协议的操作(可以说requests库就是为HTTP而生的)。

requests.request()

​ 标准形式是:requests.request(method, url, **kwargs),method对应上面的post, put等方法,而**keargs是控制参数。比如这样写:

1
2
3
kv = {'key1': 'value1','key2': 'value2'}
r = requests.request('GET', 'url', params = kv)
print(r.url)

会打印出url?key1=value1&key2=value2。最重要的是伪造header,这样写:

1
2
hd = {'user-agent':'Mozilla/5.0'}
r = requests.request('GET', 'url', heaers = hd)

其他还有json,data, cookies, auth, timeout等。

requests.get()

​ 最常见的写法:r = requests.get(url),r表示的是服务器返回的一个Response对象,url即目标网页,完整的写法是这样的:r = requests.get(url, params = None, **kwargs),后面两个为选填参数,其中params表示字典或者字节流格式,**keargs表示12个控制的参数。

​ 有意思的是,response(下面简称r)对象不仅包括服务器返回的信息,也包括requests请求的信息。r的属性主要有这么5个:

  • r.status_code: 200表示成功,404表示失败,更多状态码自己查查看。

  • r.text: HTTP响应内容的字符串形式。

  • r.encoding: 编码方式

  • r.apparent_encoding: 备选编码方式

  • r.content: HTTP响应内容的二进制形式

    其中encoding是根据header里面的charset字段来判断的,如果没有,会默认为ISO-8859-1,此编码下中文会乱码,备选编码方式一般是utf-8。

Requests库的异常

​ 嵩老师说了一句:网络连接有风险,异常处理很重要。错误有很多种,DNS查询失败,HTTP 错误,URL错误,超过最大重定向次数导致重定向异常,超时错误等等。只要状态不是200就可以认为是HTTP错误了。

通用代码框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
def getHTMLText(url):
try:
r = requests.get(url)
r.raise_for_status()#不是200就会引发异常
r.encoding = r.apparent_encoding
return r.text
except:
return "异常"
if __name__ == "__main__":
url = ""
print(getHTMLText(url))

​ 不得不说,这代码比较严谨,不像我前面两次的那样,哈哈哈,从开始就养成好习惯。

盗亦有道——robots协议

爬虫尺度分类与可能带来的问题

​ 网络爬虫是有尺度的,大概可以分为这么三种:

  • 规模小,数据量小,速度不敏感,用requests库即能完成,用来玩转网页的。

  • 中等规模,爬取速度敏感,用scrapy库,爬取系列网站。

  • 大规模,用搜索引擎,定制开发。爬取全网

    网络爬虫会带来的三个问题:

  • 性能骚扰:水平太烂的话会给服务器资源造成巨大的压力

  • 法律风险:如果爬来的数据用于商业用途可能要面对法律风险

  • 隐私泄露;爬到正常情况下无权限获取的数据可能会造成个人隐私泄露

网络爬虫的限制

来源审查:判断User-Agent,只允许友好爬虫和浏览器访问

发布公告:robots协议,告知爬虫,要求遵守。

robots协议

全名叫:Robots Exclusion Standard,网络爬虫排除标准。以协议约定的形式告知爬虫爬取权限,一般在网站的根目录下。来看一下京东的robots协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
>User-agent: *
>Disallow: /?*
>Disallow: /pop/*.html
>Disallow: /pinpai/*.html?*
>User-agent: EtaoSpider
>Disallow: /
>User-agent: HuihuiSpider
>Disallow: /
>User-agent: GwdangSpider
>Disallow: /
>User-agent: WochachaSpider
>Disallow: /
>

*代表所有,/代表根目录

原则上是应该要遵守robots协议的,但如果访问量很小,类似人的上网行为,可以不考虑遵守robots协议。但是!!!如果是商业用途或者爬取全网,必须遵守robots协议,不然出了问题是真的要背法律责任的。

举个栗子

保存一张图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import os
url = "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1832529435,1519198910&fm=11&gp=0.jpg"
root = "D://pics//"
path = root + url.split('/')[-1]
try:
if not os.path.exists(root):
os.mkdir(root)
if not os.path.exists(path):
r = requests.get(url)
with open(path, 'wb') as f:
f.write(r.content)
f.close()
print("文件保存成功")
else:
print("文件已存在")
except:
print("爬取失败")

root是目录,先检测目录是否存在,如果没有,建一个,然后路径同理,接着打开一个个文件,然后写入r.content(因为content属性是二进制形式的)

嗯?保存一张图片也许没有右键另存为来的快,但成百上千张图片可就无能为力了,特别是还要对图片进行筛选的情况下。(我存的图片男生可以参考,女生可以换一张,哈哈哈哈哈哈哈)

Beautiful Soup库入门

​ 懒得再新建文章了,直接继续第二章的内容。这次是学习Beautiful Soup库,(形象地称之为煲汤。。。。是的,没错,老师也用了这个词。。。。)自行安装

测试是否安装成功:

1
2
3
4
5
6
7
import requests
from bs4 import BeautifulSoup
r = requests.get("http://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo, 'html.parser')
print(soup.prettify())

如果打印出来的内容和HTML文档排版一样就OK。

##Beautiful Soup库的基本元素

​ 首先,Beautiful Soup库是解析、遍历、维护“标签树”的功能库,这里会对应HTML的框架(Requests库对应HTTP操作)。

​ Beautiful Soup库的引用一般是BeautifulSoup类,有意思的是,真正用的时候只要写bs4就好,from bs4 import BeautifulSoup,就是这样奇葩。而上面代码中soup = BeautifulSoup(demo, 'html.parser'),html.parser则指的是html解析器。用来解析demo的HTML结构。

​ Beautiful Soup库有如下5个基本元素:

  • Tag: 最基本的信息单元
  • Name: 标签的名字,格式:.name
  • Attributes: 标签的属性。格式:.attrs
  • NavigableString: 标签内非属性字符串。格式:.string
  • Comment: 标签内部的注释信息,一种特殊的comment类型

Tag 标签

​ 任何HTML语法中的标签都可以通过soup.tag访问,比如,soup.titlesoup.a等等当存在多个相同的tag时,返回第一个

​ 每个标签都有自己的名字,可以这样来访问:soup.a.name,还可以查看其上一级标签名字soup.a.parent.name,父亲的父亲同理,儿子的话用children就行.

​ 标签属性是字典形式,可以这样来访问:tag = soup.a,tag.attrs,打印出一个字典后,还可以查看具体的某一个属性,比如tag.attrs['class']

​ NavigableString 和 Comment不再细说,原理和这个一样。

基于bs4库的HTML内容遍历方法

​ 贴一下HTML的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>
This is a python demo page
</title>
</head>
<body>
<p class="title">
<b>
The demo python introduces several python courses.
</b>
</p>
<p class="course">
Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">
Basic Python
</a>
and
<a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">
Advanced Python
</a>
</p>
</body>
</html>

标签树的三个属性可以做到下行遍历:

  • .contents:将tag的所有子节点都存入列表
  • .children:子节点的迭代类型,用循环遍历子节点
  • .descendants:子孙节点的迭代烈性,包括所有子孙节点,用循环遍历

遍历子节点和子孙节点:

1
2
3
4
5
for child in soup.body.children:
print(child)
for child in soup.body.descendants:
print(child)

​ 上行遍历:

  • .parent:节点的父节点
  • .parents:节点父辈的迭代类型,循环遍历

上行遍历示例:

1
2
3
4
5
for parent in soup.a.parents:
if parent is None:
print(parent)
else:
print(parent.name)

考虑到会遍历到根节点(即soup本身),所以做了一个判断。

平行遍历:

  • .next_sibling:按照顺序遍历下一个平行节点
  • .previous_sibling:按照顺序遍历上一个平行节点
  • .next_siblings:迭代,循环遍历后面所有平行节点
  • .previous_siblings:迭代,循环遍历前面所有平行节点

注意一个问题:在同一个父节点下才算是平行节点。

比如,这是遍历后续节点:

1
2
for sibling in soup.a.next_sibling:
print(sibling)

友好输出:Prettify()

​ 很简单,直接print(soup.prettify()),而且支持utf-8编码.

信息标记与提取

信息标记

上表就好:

  • XML:最早的通用信息标记语言,可扩展性强但是繁琐

  • JSON:信息有类型,键值对显示,超级适合js的处理,比XML简洁

  • YAML:信息无类型,文本信息比例高,可读性很好。

    作用:

  • XML:Internet上的信息交互与传递

  • JSON:移动应用端与节点的信息通信

  • YAML:系统的配置文件

信息提取

​ 最好的方法当然是形式解析与关键字内容搜索融合。

​ 比如,要提取所有的URL链接,只需要这样:

1
2
for link in soup.find_all('a'):
print(link.get('href'))

​ find_all(name, attrs, recursive, string, **kwargs),参数分别是标签名字,标签属性,是否对子孙全部检索(默认为True),检索区域,及控制参数,既然是匹配,肯定是正则大法好咯。TIPS:(…) 等价于 .find_all(),soup(…) 等价于 soup.find_all()

再举个栗子

​ 爬取中国大学排名,很简单的栗子,直接上代码好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests
from bs4 import BeautifulSoup
import bs4
def getHTMLText(url):
try:
r = requests.get(url, timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
def fillUnivList(ulist, html):
soup = BeautifulSoup(html, "html.parser")
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):
tds = tr('td')
ulist.append([tds[0].string, tds[1].string, tds[3].string])
def printUnivList(ulist, num):
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format("排名", "学校名称", "总分",chr(12288)))
for i in range(num):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], chr(12288)))
def main():
unifo = []
url = 'http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html'
html = getHTMLText(url)
fillUnivList(unifo, html)
printUnivList(unifo, 30)#30所大学
main()

​ tplt和chr(12288)是为了使输出稍微美观一点而做的一点小优化。中间要判断是否是bs4.element.tag类型,所以引用了bs4库。(吐槽一下python的format是真的强,还有,南航居然排到了29诶。。。)

Re(正则表达式)入门

​ 前面已经有过接触,正则表达式是一种针对字符串表达“简洁”和“特征”思想的工具,用来判断某字符串的的特征归属,可以用来替换或者匹配字符串。我决定直接贴图…

​ 调用方式:import re,re库一般匹配raw string(原生字符串类型r’text’),即不包括对转义符再次转义的字符串。re库也可以直接用string类型来表示,但会更繁琐,如果正则包含转义符时,用raw string类型更好。

​ 主要函数:

​ 大部分函数都是三个参数:pattern, string, flags。pattern即为构造的正则表达式,string为待匹配字符串,flags为控制参数,其中split函数还有一个maxsplit(最大切割次数),放在string和flags中间。sub函数更特殊,re.sub(pattern, repl, string, count = 0, flags = 0), 另外常用的正则可以经过编译使用:pat = re.compile(pattern, flags), rst = pat.search('string'),可以提升运行速度。返回的是match对象,它的属性如下:

​ 属性的方法:

​ 另外,在默认返回一个对象而又能同时匹配多个字符串时,默认返回最长的那个(贪婪匹配),如果有 需要,作最短匹配,加一个?就可以了。

​ 定向爬取淘宝商品(不要不加限制地爬,robots协议不允许….)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
import re
def getHTMLText(url):
try:
r = requests.get(url, timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
def parsePage(ilt, html):
try:
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"', html)
tlt = re.findall(r'\"raw_title\"\:\".*?\"', html)
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price, title])
except:
print("")
def printGoodsList(ilt):
tplt = "{:4}\t{:8}\t{:16}"
print(tplt.format("序号", "价格", "商品名称"))
count = 0
for g in ilt:
count = count + 1
print(tplt.format(count, g[0], g[1]))
def main():
goods = '书包'
depth = 3
start_url = 'https://s.taobao.com/search?q=' + goods
infoList = []
for i in range(depth):
try:
url = start_url + '&s=' + str(44 * i)
html = getHTMLText(url)
parsePage(infoList, html)
except:
continue
printGoodsList(infoList)
main()

​ 上一段比较优美的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import requests
from bs4 import BeautifulSoup
import traceback
import re
def getHTMLText(url, code="utf-8"):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = code
return r.text
except:
return ""
def getStockList(lst, stockURL):
html = getHTMLText(stockURL, "GB2312")
soup = BeautifulSoup(html, 'html.parser')
a = soup.find_all('a')
for i in a:
try:
href = i.attrs['href']
lst.append(re.findall(r"[s][hz]\d{6}", href)[0])
except:
continue
def getStockInfo(lst, stockURL, fpath):
count = 0
for stock in lst:
url = stockURL + stock + ".html"
html = getHTMLText(url)
try:
if html=="":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div',attrs={'class':'stock-bets'})
name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})
keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val
with open(fpath, 'a', encoding='utf-8') as f:
f.write( str(infoDict) + '\n' )
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
except:
count = count + 1
print("\r当前进度: {:.2f}%".format(count*100/len(lst)),end="")
continue
def main():
stock_list_url = 'http://quote.eastmoney.com/stocklist.html'
stock_info_url = 'https://gupiao.baidu.com/stock/'
output_file = 'D:/BaiduStockInfo.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)
main()

​ 动态优化好评。