爬虫基础

requests库

requests库是一个常用的用于http请求的模块,它使用python语言编写,可以方便的对网页进行爬取,是学习python爬虫的较好的http请求模块。

requests库的7个主要方法

名称 描述 参数
requests.request() 构造一个请求 requests.request(method,url,[**kwarges])
requests.get() 请求获取url位置的资源 r=request.get(url,params,**kwargs)
requests.head() 请求获取资源的头部信息 requests.head(url,**kwargs)
requests.post() 请求向url位置的资源后附加新的数据 requests.post(url,data,json,**kwargs)
requests.put() 请求向url位置存储一个资源覆盖原有的资源 requests.put(url,data,**kwargs)
requests.patch() 请求改变该资源的部分内容 requests.patch(url,data,**kwargs)
requests.delete() 请求删除url位置存储的资源 requests.delete(url,**kwargs)

因为方法过多不一一列举,以request方法为例:

requests.request(method,url,[**kwarges]) :

  • method:请求方式(get,post,put,patch,head,delete,option)
  • url:url链接
  • **kwarges:
    • params[字典或字节序列,作为参数增加到url中]
    • data[字典,字节序列或文件对象,作为request的内容]
    • json[json格式数据,作为request的内容]
    • headers[字典,HTTP头]
    • cookies[字典或cookiejar,request中的cookie]
    • auth[元组,支持http认证功能]
    • files[字典,传输文件]
    • timeout[设定超时时间,以s为单位]
    • proxies[字典类型,设定代理服务器,可增加登录认证]
    • allow_redirects[重定向开关,默认为True]
    • stream[获取内容立即下载开关,默认为True]
    • verify[认证ssl证书开关,默认为True]
    • cert[本地ssl证书路径]

response对象

请求requests会构造一个请求资源的requests对象,服务器则会返回一个包含所请求内容的response对象:

名称 描述
r.status_code http请求的返回状态
r.text HTTP响应内容的字符串形式
r.encoding 从http header 中猜测的相应内容编码方式
r.apparent_encoding 从内容中分析出的响应内容的编码方式(备选编码方式)
r.content http响应内容的二进制形式

requests库的异常

名称 描述
requests.ConectionError 网络连接异常,如DNS查询失败、拒绝连接等
requests.HTTPError HTTP错误异常
requests.URLRequired URL缺失异常
requests.TooManyRedirects 超过最大重定向次数
requests.ConnectTimeout 连接远程服务器超时
requests.Timeout 请求url超时

爬取网页的通用代码框架

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
def getHTMLText(url):
#网络连接有风险,异常处理很重要
try:
r = requests.get(url,timeout=30)
r.raise_for_status() #如果状态不是200,引发HTTPError异常
r.encoding = r.apparent_encoding
return r.text
except:
return "产生异常"
if __name__=="__main__":
url = "http://www.baidu.com"
print(getHTMLText(url))

一些实例代码

京东商品爬取页面实例

1
2
3
4
5
6
7
8
9
import requests
url = "https://item.jd.com/2967929.html"
try:
r =requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
print(r.text[:1000])
except:
print("爬取失败")

亚马逊商品页面的爬取

1
2
3
4
5
6
7
8
9
import requests
url = "https://www.amazon.cn/gp/product/B01M8L5Z3Y"
try:
kv = {'usre-agent':'Mozilla/5.0'} #更改user-agent头
r=requests.get(url,headers=kv)
r.encoding=r.apparent_encoding
print(r.text[:1000])
except:
print("爬取失败")

百度/360搜索关键词提交

1
2
3
4
5
6
7
8
9
10
import requests
keyword = "Python"
try:
kv = {'wd':keyword}
r = requests.get("http://www.baidu.com/s",params=kv)
print(r.request.url)
r.raise_for_status()
print(len(r.text))
except:
print("爬取失败")
1
2
3
4
5
6
7
8
9
10
import requests
keyword = "Python"
try:
kv = {'q':keyword}
r = requests.get("http://www.so.com/s",params=kv)
print(r.request.url)
r.raise_for_status()
print(len(r.text))
except:
print("爬取失败")

网络图片的爬取和存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import os
url = "https://www.baidu.com/img/bd_logo1.png"
path = url.split('/')[-1]
try:
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("爬取失败")

IP地址归属地的自助查询

1
2
3
4
5
6
7
8
9
import requests
url = "http://m.ip138.com/ip.asp?ip="
try:
r = requests.get(url+'180.97.33.107') #baidu的IP
r.raise_for_status()
r.encoding = r.apparent_encoding
print(r.text[-500:])
except:
print("爬取失败")

robots协议

robots.txt

一般放置在网站的根目录下,用于告诉爬虫哪些网站可以抓取,哪些不行

robots协议的基本语法:

  • User-agent:允许访问的爬虫

爬虫抓取时会声明自己的身份,这就是User-agent,没错,就是http协议里的User-agent,robots.txt利用User-agent来区分各个引擎的爬虫。

举例说明:百度网页搜索爬虫的User-agent为baiduspider,下面这行就指定百度的爬虫。

1
2
User-agent:baiduspider	//指定百度的爬虫。
User-agent: * //指定所有的爬虫

关于爬虫的User-agent在网上有相应的列表,比如说google爬虫列表,百度爬虫列表

  • Disallow:不允许爬虫访问的目录

Disallow行列出的是不允许爬虫访问的网页,以正斜线 (/) 开头,可以列出特定的网址或模式。要屏蔽整个网站,使用正斜线即可,如:Disallow: /

  • 举例
1
2
3
4
User-agent: baiduspider	#允许百度
Disallow: /www/ #禁止访问www目录
User-agent: Googlebot #允许谷歌
Disallow: /root/index.html #禁止访问root下的index.html

robots协议的使用

  1. 网络爬虫通过自动或人工识别robots.txt,再进行内容爬取
  2. robots协议是建议但非约束性,网络爬虫可以不遵守,但存在法律风险。

信息标记的三种形式

xml

由HTML扩展而来的通用信息标记形式,扩展性好,但繁琐

1
2
3
4
5
6
7
8
9
10
<person>
<firstName>Tian</firstName>
<lastName>Song</lastName>
<address>
<streeAddr>中关村南大街5号</streetAddr>
<city>北京市</city>
<zipcode>100081</zipcode>
</address>
<prof>Computer System</prof><prof>Security</prof>
<person>

json

有类型的键值对,适合程序处理(js),较xml简洁

"key":"value"
"key":["value1","value2"]
"key":{"subkey":"subvalue"}

1
2
3
4
5
6
7
8
9
10
{
"firstName":"Tian",
"lastNmae":"Song",
"address":{
"streetAddr":"中关村南大街5号",
"city":"北京市",
"zipcode":"100081"
},
"prof":["Computer System","Security"]
}

yaml

无类型键值对 key:value,可读性好

缩进表达所属关系,由|表达整块数据,#表示注释,-表示并列的值信息

1
2
3
4
5
6
7
8
9
firstName : Tian
lastName : Song
address :
atreeAddr :中关村南大街5号
city:北京市
zipcode:100081
prof :
-Computer System
-Security

BeautifulSoup库学习

Beautiful Soup 是 python 的一个库,最主要的功能是从网页抓取数据。

Beautiful Soup 提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。若文档没有指定一个编码方式仅仅需要说明一下原始编码方式就可以了。

推荐使用 BeautifulSoup4,不过它已经被移植到BS4 了,也就是说导入时我们需要 import bs4 。

下面以一个实例开始学习:

1
2
3
4
5
6
7
8
9
10
from bs4 import BeautifulSoup #导入BeautifulSoup,注意大小写
import requests

url = 'http://www.baidu.com'
r = requests.get(url)
r.encoding = r.apparent_encoding #从内容中分析出的响应内容的编码方式
#创建 beautifulsoup 对象,参数1可传入字符串或文件句柄,参数2指定解析器
soup = BeautifulSoup(r.text,'lxml')
#打印文档类容
print(soup.prettify()) #.prettify()对内容html文本内容进行美化输出

解析器

选择最合适的解析器来解析这段文档。

解析器 使用方法 条件
bs4的html解析器 BeautifulSoup(mk,’html.parser’) 安装bs4
lxml的html解析器 BeautifulSoup(mk,’lxml’) pip install lxml
lxml的xml解析器 BeautifulSoup(mk,’xml’) pip install lxml
html5lib的解析器 BeautifulSoup(mk,’html5lib’) pip install html5lib

BeautifulSoup的基本元素

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为 4 种:

基本元素 说明
Tag 标签,分别用<>和</>开头和结尾.<tag>(只列出第一个满足条件的元素)
Name 标签的名字,格式:<tag>.name
Attrbutes 标签的属性,格式:<tag>.attrs
NavigableString 标签内非属性字符串,<tag>.string
Comment 标签内字符串的注释部分,是一种特殊的NavigableString对象,其输出不包括注释符号.

示例代码:

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
from bs4 import BeautifulSoup #导入BeautifulSoup,注意大小写
import requests

url = 'http://www.baidu.com'
r = requests.get(url)
r.encoding = r.apparent_encoding #从内容中分析出的响应内容的编码方式
#创建 beautifulsoup 对象
soup = BeautifulSoup(r.text,'lxml')
#用 Beautiful Soup 来方便地获取 Tags。
#可以看到:只列出第一个满足条件的元素
print(soup.title)
print(soup.head)
print(soup.a)
print(soup.p)
print()
#Name
print(soup.name)
print(soup.title.name)
print(soup.a.name)
print()
#Attrbutes
print(soup.title.attrs)
print(soup.a.attrs)
print()
#NavigableString
print(soup.title.string)
print(soup.a.string)

comment:

Comment 对象是一个特殊类型的 NavigableString 对象。如果 HTML 页面中含有注释及特殊字符串的内容。而那些内容不是我们想要的,所以我们在使用前最好做下类型判断,然后再进行其他操作,如打印输出。

1
2
3
4
5
6
7
8
from bs4 import BeautifulSoup #导入BeautifulSoup,注意大小写
from bs4 import element

text = '<li><!--注释--></li>'
soup = BeautifulSoup(text,'lxml')

if type(soup.li.string) == element.Comment:
print(soup.li.string)

遍历文档树

属性 说明
.contents 子节点的列表,将<tag>所有的儿子节点存入列表
.children 子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
.descendants 子孙节点的迭代类型,包含所有的子孙节点,用于循环遍历
.parent 节点的父亲标签
.parents 节点的先辈标签的迭代类型,用于循环遍历先辈节点
.next_sibling 返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
.next_siblings 迭代类型,返回按照html文本顺序的后续所有平行节点标签
.previous_siblings 迭代类型,返回按照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
25
26
27
from bs4 import BeautifulSoup #导入BeautifulSoup,注意大小写
import requests

url = 'http://www.baidu.com'
r = requests.get(url)
r.encoding = r.apparent_encoding #从内容中分析出的响应内容的编码方式
#创建 beautifulsoup 对象
soup = BeautifulSoup(r.text,'lxml')

print(type(soup.head.contents))
print()
print(type(soup.head.children))
print()
print(type(soup.head.descendants))
print()
print(type(soup.head.parent))
print()
print(type(soup.div.next_sibling))
print()
print(type(soup.p.previous_sibling))
print()
print(type(soup.p.next_siblings))
print()
print(type(soup.p.previous_siblings))
#遍历
for i in soup.head.contents:
print(i)

搜索文档树

find_all()

find_all 是用于搜索节点中所有符合过滤条件的节点,它支持的过滤器的类型有:字符串,正则表达式,列表,True,方法。find_all 的参数为:find_all( name , attrs , recursive , text , **kwargs )

代码示例:

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
from bs4 import BeautifulSoup #导入BeautifulSoup,注意大小写
import requests

url = 'http://www.baidu.com'
r = requests.get(url)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text,'lxml')

#name参数:可以查找所有名字为name的tag, 字符串对象会被自动忽略掉
p = soup.find_all("p")
print(p)
print()
#keyword参数:对标签属性值的检索字符串,可标注属性检索
p = soup.find_all(id='lh')
#注意有些属性在python中属于关键字,需要处理。例如class,传参时用class_(加了下划线)
p2 = soup.find_all(class_='bri')
print(p)
print(p2)
#有些属性不能通过以上方法直接搜索,比如html5中的data-*属性
#不过可以通过attrs参数指定一个字典参数来搜索包含特殊属性的标签
#如:soup.find_all(attrs={"data-foo": "value"})
print()
#text参数:检索文档中的字符串内容, 与 name 参数的可选值一样,text参数接受 字符串,正则表达式,列表,True
p = soup.find_all(text="百度一下,你就知道")
print(p)
print()
#limit参数:限制返回结果的数量, 效果与SQL中的limit关键字类似
p = soup.find_all("p",limit=1)
print(p)
print()
#recursive 参数:是否对子孙全部检索,默认为True
p = soup.html.find_all("title", recursive=False)
print(p)

扩展方法

方法 描述
<>.find() 搜索且只返回一个结果,字符串类型,同find_all()参数
<>.parents() 在先辈节点中搜索,返回列表类型,同find_all()参数
<>.parent() 在先辈节点中返回一个结果,字符串类型,同find()参数
<>.find_next_sibling() 在后序平行节点中返回一个结果,字符串类型,同find()参数
<>.find_next_siblings() 在后序平行节点中搜索,返回列表,同find_all()参数
<>.find_previous_sibling() 在前序平行节点中返回一个结果,字符串类型,同find()参数
<>.find_previous_siblings() 在前续平行节点中搜索,返回列表,同find_all()参数

CSS选择器

这就是另一种与 find_all 方法有异曲同工之妙的查找方法.

写 CSS 时,标签名不加任何修饰,类名前加.,id名前加#,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from bs4 import BeautifulSoup
import requests
url = 'http://www.baidu.com'
r = requests.get(url)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text,'lxml')
#通过标签名查找
print(soup.select('head'))
print('='*50)
#通过类名查找
print(soup.select('.fm')) #注意.号
print('='*50)
#通过id名查找
print(soup.select('#cp')) #注意#号
#组合查找
print('='*50)
print(soup.select('p#cp'))
#组合查找2
print('='*50)
print(soup.select('form[class=fm]'))

requests-bs4爬虫实例

实例1:大学排名信息爬虫

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
#链接:http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html
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") #创建beautifulsoup对象
#print(soup.find('tbody'))
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag): #isinstance()函数来判断一个对象是否是一个已知的类型
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))) #chr(12288)为全角空格,利于中文对齐
for i in range(num):
u=ulist[i]
print(tplt.format(u[0],u[1],u[2],chr(12288)))

def main():
uinfo = []
url = 'http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html'
html = getHTMLText(url)
fillUnivList(uinfo, html)
printUnivList(uinfo, 20) # 指定前20
main()

实例2:股票数据定向爬虫

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
66
67
68
69
import requests
from bs4 import BeautifulSoup
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 = 'http://gupiao.baidu.com/stock/'
output_file = 'output.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)
return

main()

正则表达式

常用操作符

操作符 说明
. 表示任意单个字符
[ ] 字符集,对单个字符给出取值范围
[^ ] 非字符集,对单个字符给出排除范围
* 前一个字符0次或无限次扩展
+ 前一个字符1次或无限次扩展
? 前一个字符0次或1次扩展
| 左右表达式任意一个
{m} 扩展前一个字符m次
{m,n} 扩展前一个字符m至n次(含n)
^ 匹配字符串开头
$ 匹配字符串结尾
() 分组标记,内部只能用`
\d 数字,等价于[0-9]
\w 单词字符,等价于[A-Za-z0-9]

re库默认采用贪婪匹配,要使用最小匹配,操作符如下:

操作符 说明
*? 前一个字符0次或无限次扩展,最小匹配
+? 前一个字符1次或无限次扩展,最小匹配
?? 前一个字符0次或1次扩展,最小匹配
{m,n}? 扩展前一个字符m至n次(含n),最小匹配

re库

re库主要功能函数

函数 说明 参数
re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 re.search(pattern,string,flags=0)
re.match() 从一个字符串的开始位置起匹配正则表达式,返回match对象 re.match(pattern,string,flags=0)
re.findall() 搜索字符串,以列表类型返回全部能匹配到的子串 re.findall(pattern,string,flags=0)
re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 re.split(pattern,string,maxsplit=0,flags=0)
re.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素都是match对象 re.finditer(pattern,string,flags=0)
re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 re.sub(pattern,repl,string,count=0,flags=0)

参数解释:

  • pattern - 正则表达式的字符串或原生字符串的表示
  • string - 待匹配的字符串
  • flags - 控制标记
  • maxsplit - 最大分割数,剩余部分作为一个元素输出
  • repl - 用于替换的子字符串
  • count - 匹配的最大替换次数

控制标记(flags):

flags 说明
re.I 忽略正则表达式的大小写,[A-Z]可匹配小写字符
re.M 正则中的^可将给定字符串的每行当做匹配的开始
re.S 正则表达式中的点(.)操作符匹配所有字符,默认不匹配换行

另一种等价用法

1
2
3
4
>>> rst = re.search(r'[1-9]\d{5}', 'BIT 100081')	# 一次性操作

>>> pat = re.compile(r'[1-9]\d{5}') # 编译后多次操作
>>> rst = pat.search('BIT 100081')

match对象

  • match对象的属性
属性 说明
.string 待匹配的文本
.re 匹配时使用的pattern对象(正则表达式)
.pos 正则表达式搜索文本的开始位置
.endpos 正则表达式搜索文本的结束位置
  • match对象的方法
方法 说明
.group(0) 获得匹配后的字符串
.start() 匹配字符串在原始字符串的开始位置
.end() 匹配字符串在原始字符串的结束位置
.span() 返回(.start(),.end())

反反爬策略

常见的反爬虫策略:检查User-Agent,检查访问频率,封掉异常IP,设置验证码,Ajax异步加载

相对应的策略:

  • 动态修改UA头

  • 修改爬虫访问周期

  • 使用代理池

  • 模仿自然人行为:selenium

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • © 2020 h0ryit
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信