python爬虫入门
爬虫领域并没有非常成熟,互联网中已经存在了Robots协议,但是法律部分仍在建立完善中。
前面的话
声明:由于本文是笔者根据MOOC学习写的博客,所以本文拒绝以任何形式进行盈利,仅供学习研究使用!转载请注明文章来源。
对于爬虫本身的安全问题:用于个人使用或科研范畴,基本不存在问题。
数据用于商业盈利,那么就事而论,可能违法,可能不违法。
对于学习:建议收藏(狗头保命),可以多看看相关的文章,笔者是看着MOOC的嵩天老师的课学习的。然后一定要多敲代码,不可眼高手低,遇到不懂的首先是自己查,去百度,多看几篇文章,或者去那些学习交流群里问(不要老是和我一样去水群)
0.1Robots协议解读
网站通过Robots协议来告诉搜索引擎哪些页面是可以爬取,哪些不能。
该协议是国际互联网界的道德规范,虽然没有写进法律,但是每个爬虫都要去遵守。可以不遵守,但会有法律风险。其实每一个爬虫都应该遵守Robots协议。
当然,如果我们写一个小程序,每天仅访问服务器几次,可以不遵守Robots协议,就和我们人类类似,类人类行为可以不参考Robots协议。
网络爬虫,盗亦有道。
User-agent: Baiduspider #百度搜索引擎
#User-agent: *表示对于所有的搜索引擎
Allow:/.../ #允许访问/.../12345.com 或 /... .htm
Disallow:/.../ #禁止访问/.../12345.com
Disallow:/ #禁止访问除Allow以外的所有页面
淘宝的robots协议:
User-agent: Baiduspider
Disallow: /
User-agent: baiduspider
Disallow: /
百度浏览器搜索淘宝之后:
这是因为,Disallow: /禁止了除Allow允许的URL爬取之外所有的页面
当我们爬取网站的数据的时候,无论是否供个人使用,都应该遵守Robots协议,当一个网站并没有提供Robots协议就表示这个网站允许所有的爬虫任意爬取
0.2网络爬虫的约束
我们使用爬虫的时候要对自己的爬虫有一个合理的频率约束,因为频率过快的爬虫会对服务器造成大的压力,网站可能因此ban掉我们的IP甚至采取法律行动,所以我们要把自己的爬虫的速度约束到一个合理的范围内。
0.3基本流程
网络爬虫的流程:
获取网页→解析网页(提取数据)→存储数据
获取网页:给一个网址发送请求,该网址返回整个网页的数据(可以理解为,输入一个网址,看到这整个网页)
解析网页:从整个网页的数据中提取自己想要的数据。
储存数据:把数据存储下来,储存在csv,txt,mysql等等里
0.4三个流程的实现需要什么
1.获取网页
request、urllib、selenium(模拟浏览器)、多进程多线程爬取,登录爬取,突破ip禁封和服务器爬取
2.解析网页
re正则表达式、BeautifulSoup和lxml、解决中文乱码
3.储存数据
存入txt文件、写入csv文件、存入MySQL数据库和存入MongoDB数据库
一、Requests库入门
利用Requests可以快速爬取某个网页,是一种小规模爬虫
1.1 Requests库的get()方法
r = requests.get(url)
'''get()构造一个向服务器请求资源的Requests对象
requests是返回了一个Response对象,最后给变量r,r是一个Response对象,返回整个页面所有的响应内容'''
Response对象的属性
属性 | 说明 |
---|---|
r.status_code | HTTP请求的返回状态,200表示连接成功,其他表示失败 |
r.text | HTTP响应内容的字符串形式,即,url对应的页面信息 |
r.encoding | 从HTTPheader中猜测的响应内容编码格式 |
r.apparent_encoding | 从内容中分析出响应内容编码方式(备选编码方式) |
r.content | HTTP响应内容的二进制形式 |
1.2 爬取网页的通用代码框架
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"产生异常"
#别的代码在import此模块(此py文件)的时候如果不想让此模块中的某些语句执行或者没有执行必要的就可以放在if__name__ = "__main__":中去
if __name__ == "__main__":
url = "http://www.baidu.com"
print(getHTMLText(url))
1.3 HTTP协议及Requests库的主要方法
1.3.1 HTTP协议
HTTP协议
超文本传输协议
是一个基于“请求与响应”模式的、无状态的应用层协议。无状态是说每一次请求和每一次请求相互独立不相关,应用层协议是因为在TCP协议之上运行
超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII形式给出;而消息内容则具有一个类似MIME的格式。这个简单模型是早期Web成功的有功之臣,因为它使开发和部署非常地直截了当。
链接:HTTP协议百度百科
HTTP协议采用URL定位网络资源的标识
URL格式:http://host[:port][path]
以http://开头
第一个部分host
需是一个合法的Internet主机域名或ip地址
第二个部分[:port]
指端口号,可省略(默认端口号80)
第三部分[path]
是在此服务器请求资源的内部路径
HTTP协议对资源的操作
方法 | 说明 |
---|---|
GET | 请求获取URL位置的资源 |
HEAD | 请求获取URL位置资源的响应消息报告,即获取该资源的头部信息 |
POST | 请求向URL位置的资源后附加新的数据 |
PUT | 请求 向URL位置存储一个资源,覆盖原URL位置的资源 |
PATCH | 请求局部更新URL位置的资源,即改变该出资源的部分内容 |
DELETE | 请求删除URL位置存储的资源 |
1.3.2 Requests库的七个方法
对应的,Requests就有了七个方法。
Requests库的七个方法
方法 | 说明 |
---|---|
requests.request() | 构造一个请求,支撑以下各方法的基础方法 |
requests.get() | 获取HTML网页的主要方法,对于HTTP的GET |
requests.head() | 获取HTML网页的头部信息的方法 ,对应于HTTP的HEAD |
requests.post() | 向HTML网页提交POST请求的方法,对应于HTTP的POST |
requests.put() | 向HTML网页提交PUT请求方法,对应于HTTP的PUT |
requtsts.patch() | 向HTML网页提交局部修改请求,对应于HTTP的PATCH |
requests.delete() | 向HTML网页提交删除请求,对应于HTTP的DELETE |
(1).request方法
requests.request(method,url,**kwargs)
method:请求方式,对应get/put/post等7种
url : 拟获取页面url链接
**kwargs:控制访问的参数,共13个
(2).对于**kwargs参数(控制访问参数,可选参数):
1). params:字典或者字节序列,作为参数增加到url中
import requests
kv = {'key1':'value1','key2':'value2'}
r = requests.request('GET','http://python123.io/ws',params=kv)
print(r.url)
输出结果:
2). data:字典、字节序列或文件对象,作为Request的内容
kv = {'key1':'value1','key2':'value2'}
body = '主体内容'
r = requests.request('POST','http://python123.io/ws',data=kv)
r = requests.request('POST','http://python123.io/ws',data=body)
这个时候会把字典和字符串作为数据存到url所对应位置的数据域中去
3). json:json格式的数据,作为Request的内容
kv = {'key1':'value1','key2':'value2'}
r = requests.request('POST','http://python123.io/ws',json=kv)
字典会存到服务器的json域上
4). headers:字典,HTTP制定头
hd = {'user-agent':'Chrome/10'}
r = requests.request('POST','http://python123.io/ws',headers = hd)
此时再去访问的时候服务器看到的就是user-agent就是chrom/10(chrom服务器10版本),模拟浏览器的方法-header字段实现
5).cookies:字典或CookieJar,Request中的cookie
6).auth:元组,支持HTTP认证功能
7).files: 字典类型,传输文件时使用
向服务器传输文件时使用的字段
fs = {'file':open('data.xls','rb')}#定义一个字典
r = requests.request('POST','http://python123.io/ws',files = fs )
向某一个链接提交一个文件
8).timeout:设定超时时间,秒为单位
发起一个get请求的时候可以设定一个timeout时间,如果在timeout时间内,请求没有返回回来,就会产生一个timeout的异常
r = requests.request('GET','http://www.baidu.com',timeout = 10)
9).proxies:字典类型,设定访问代理服务器,可以增加登录认证
可以为我们爬取网页增加设定相关的访问代理服务器
pxs = {'http':'http://user:pass@10.10.10.1:1234'
,'https':'https://10.10.10.1:4321'}
r = requests.request('GET','http://www.baidu.com',proxies = pxs)
http代理访问过程中增加用户名和密码的设置,或者增加一个https的代理,这样我们在访问百度的时候就是使用的代理服务器的ip地址(可以有效的隐藏用户爬取网页的源的ip地址的信息,有效防止对爬虫的逆追踪)
10).allow_redirects:True/False
默认为True,重定向开关
11). stream:True/False
默认为True,对获取内容下载开关,是否立即下载
12). verify:True/False
默认为True,认证SSL证书开关
13). cert:本地SSL证书路径
1.4 实例一:爬取京东商品页面
爬取:https://item.jd.com/100009077475.html的商品
import requests
kv = {'user-agent':'Mozilla/5.0'}
def getHTMLText(url):
try:
r = requests.get(url,headers = kv,timeout = 30)
r.raise_for_status()#如果状态不是200,引发HTTPError异常
r.encoding = r.apparent_encoding
return r.text
except:
return"产生异常"
if __name__ == "__main__":
url = "https://item.jd.com/100009077475.html"
print(getHTMLText(url))
1.5实例二:向百度搜索提供搜索关键字
通过提交接口来进行搜索https://www.baidu.com/s?wd=京东
这句话就等于在百度一下中百度搜索 京东
关键字
import requests
keyword = "python"
try :
kv = {'wd':keyword}
r = requests.get("https://www.baidu.com/",params=kv)
print(r.request.url)
r.raise_for_status()
print(len(r.text))
except:
print("爬取失败")
或者360提供关键字搜索
import requests
keyword = "python"
try :
kv = {'q':keyword}
r = requests.get("https://www.so.com/",params=kv)
print(r.request.url)
r.raise_for_status()
print(len(r.text))
except:
print("爬取失败")
1.6实例三:爬取图片
import requests
import os
url = "https://img0.baidu.com/it/u=961067489,3842050517&fm=26&fmt=auto&gp=0.jpg"
root = "E://spyder_resources//picture//"
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("爬取失败")
爬取图片/视频/音频等应该都是类似的,改动该框架的某些东西就可通用。这个实例先只说爬取图片。
1.7实例四:查询IP地址
py中没有这样的库,而有一个网站适合我们去使用,我们可以使用requests库来提交一个ip地址进行查询
观察在提交ip数据后的两者变化,从而推断出如何使用requests库来进行ip提交,这种的其实都是以链接的方式对后台进行提交的。
注意:在返回r.text的时候,如果内容过长就会导致idle失效,所以可以输出部分内容比如输出r.text[-500]或者[0:1000]
import requests
hd = {'user-agent':'Chrome/10'}#伪装一下自己
url = "https://www.ip138.com/iplookup.asp?ip=202.204.80.112&action=2"
try :
r = requests.get(url,headers = hd)
r.raise_for_status()
r.encoding = r.apparent_encoding
print(r.text)
except:
print("爬取失败")
二、Beautiful Soup库—信息提取
首先安装四个包,bs4、lxml、html5lib这几个库
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。
2.1 Beautiful Soup库的基本元素
解析器 | 使用方法 | 所用到的库 |
---|---|---|
bs4的HTML解析器 | BeautifulSoup(mk,‘html.parser’ ) | bs4 |
lxml的HTML解析器 | BeautifulSoup(mk,‘lxml’) | lxml |
lxml的XML解析器 | BeautifulSoup(mk,‘xml’) | lxml |
html5lib的解析器 | BeatuifulSoup(mk,‘html5lib’) | html5lib |
基本元素 | 说明 |
---|---|
Tag | 标签,最基本的信息组织单元,分别用<>和</>标明开头和结尾 |
Name | 标签的名字,< p>…< /p>的名字是’p’,格式< tag >.name |
Attributes | 标签的属性,字典形式组织,格式:< tag >.attrs |
NavigableString | 标签内非属性字符串,< >…< /> ,格式:< tag >.string,表示尖括号之间的字符串 |
Comment | 标签内字符串的注释部分,一种特殊的Comment类型 |
获得< tag >标签的方法
下面来看一个HTML:
<html>
<head>
<title> 第一个页面</title>
</head>
<body>
键盘敲烂工资过万
</body>
</html>
这是由一对对尖括号组成的“标签树”,而,bs(BeautifulSoup)就是来解析遍历维护这个标签树的功能库
2.2 bs4库遍历HTML
所有的html或者xml文件都是树形结构的
因此有三种遍历方式:
1,从根节点往下向叶子结点进行遍历
2,从叶子结点向上向根结点进行遍历
3,结点间的平行遍历(同一层次/深度的结点们)
标签树的下行遍历:
属性 | 说明 |
---|---|
.content | 子节点的列表,将< tag >所有儿子结点存入列表 |
.children | 子节点的迭代类型,与.contents类似,用于循环遍历儿子结点 |
.descendants | 子孙节点的迭代类型,包含所有的子孙节点,用于循环遍历 |
标签树的上行遍历
属性 | 说明 |
---|---|
.parent | 结点的父亲标签 |
.parents | 结点先辈标签的迭代类型,用于循环遍历先辈结点 |
标签树的平行遍历
属性 | 说明 |
---|---|
.next_sibling | 返回按照HTML文本顺序的下一个平行结点标签 |
.prebious_sibling | 返回按照HTML文本顺序的上一个平行结点标签 |
.next_siblings | 迭代类型,返回按照HTML文本顺序的所有平行标签的结点标签 |
.previous_siblings | 迭代类型,返回按照HTML文本顺序的前续所有平行节点标签 |
2.3 bs4库的HTML格式化和编码
1.prettify()方法:
这个方法会在每个标签后面加一个换行符,可以将标签树以非常美观易懂易于分析的方式展现出来(prettify之后print)。
2.编码问题,bs4会将所有读入的html文件或字符串自动动转化为utf-8
三、信息组织和提取
3.1 三种信息标志形式及其比较
1.XML(可以理解为对HTML的拓展)
使用尖括号和标签来标记信息的一种格式
<name>...</name>
<name/>
<!-- 注释 -->
我们看一个XML的例子 :
<person>
<firstName> Hong </firstName>
<lastName> Xiao </lastName>
<address>
<stressAddr>1号街</stressAddr>
<city> 上海 </city>
</adress>
<prof>Computer System </prof><prof>Security</prof>
</person>
2.JSON
使用一种有类型的键值对标记信息的标记形式
"key":"vlaue"如果value是字符串就用双引号,如果是数字比如123,就直接写就行
"key":["value1","value2"]
"key":{"subkey":"subvalue"}
实例:
{
"firstname" : "Hong"
"lastname" : "Xiao"
"address" : {
"stressaddr":一号街
"city":上海
}
"prof" : ["computer system","security"]
}
3.YAML
使用无类型尖括号标记信息的标记形式
key : value
key : | #Comment加一个竖线表示它的值很长,一个块
key :
-value1
-value2#使用-表示并列
key :
subkey : subvalue#和python类似,使用缩进表示所属关系
还是小红的实例,现在用YAML形式看下:
fistname: Hong
lastname: Xiao
address :
streetaddr:一号街
city :上海
prof:
-computer system
-security
下面来好好琢磨比较一下三者:
类型 | 比较分析 | |
---|---|---|
XML | 最早的通用的信息标记语言,可扩展性好,但是繁琐 | Internet上的信息传递和交互 |
JSON | 信息有类型,在写的时候需要考虑,适合程序处理(本身就是一段程序),较XML简洁 | 移动应用云端和节点的信息通信,无注释 |
YAML | 信息无类型,文本信息比例最高,可读性好 | 各类系统的配置文件,有注释且本身可读性比较好 |
不能说只用选用哪一个,只能说什么时候用哪个,什么时候用哪个比较合适,因为他们各有各的特点
3.2 信息提取的一般方法
指从标记的信息(上面说的XML、JSON、YAML三种标记方法标记后的信息)中进行信息提取。
XML、JSON、YAML无论哪种信息标记,信息标记都是包括信息和标记两个内容
方法一:
完整解析信息的标记形式,在提取关键信息
使用标记解析器去解析XML、JSON、YAML三种格式(比如bs4库中对标签树的表里)
优点:信息解析准确
缺点:比较繁琐,速度慢
方法二:
无视任何标记形式,直接去搜索关键信息
对信息的文本使用查找函数进行查找就可
优点:提取过程简洁,速度较快
缺点:提取结果准确性与信息内容相关
方法三:
融合前两个方法
3.3 bs4库的HTML内容查找方法
<>.find_all(name,attrs,recursive,string,**kwargs)方法
返回一个列表类型,储存查找的结果
各个参数:
-
name:对标签名称的检索字符串
例如:soup.find_all('a')或soup.find_all(['a','b'])
如果给的标签名称是True,就会显示当前soup所有的标签信息,如果想要查找标签以a为开头的标签信息,就要import re(正则表达式库) -
attrs:对标签属性值的检索字符串,可以标注属性检索
import re
soup.find_all(id = re.compile('link'))#使用正则表达式输出标签id属性为link或者包含link(比如link_1、link_b)的标签信息
- recursive:是否对子孙全部检索,默认为True
也就是说我们默认搜索的是从某个标签开始,后面所有子孙节点的标签信息。 - string:<>…</>中字符串区域的检索字符串
import requests
import re#正则表达式库
import bs4
from bs4 import BeautifulSoup
hd = {'user-agent':'Chrome/10'}
url = "https://www.ip138.com/iplookup.asp?ip=202.204.80.112&action=2"
demo = '''<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 href="http://www.icourse163.org/course/BIT-268001" class="py1" id="link1">Basic Python</a> and <a href="http://www.icourse163.org/course/BIT-1001870001" class="py2" id="link2">Advanced Python</a>.</p>
</body></html>'''
soup = BeautifulSoup(demo, 'html.parser')#首先做好一锅汤——解析步骤
print(soup.prettify())#将soup后(demo变量中的html解析后)“完美”输出(加\n换行输出)
#提取信息find_all(name,attrs,recursive,string,**args)函数
print(soup.find_all('a'))#输出name为a的标签或a开头的标签信息
print(soup.find_all(id = "link1"))#输出属性值id = "link1"的标签信息
print(soup.find_all(id = re.compile("link")))#利用正则表达式输出 属性值id包含 link 的所有标签信息
print(soup.find_all(string = "Basic Python"))#检索字符串域为“Basic Python"检索出来
print(soup.find_all(string = re.compile("Python")))#利用正则表达式输出所有包含Python的字符串,放到一个列表中输出
print("\n")
print(soup("a"))#可以发现soup.find_all(..)等价于soup(..)
print(soup(id = "link1"))
print(soup(id = re.compile("link")))
find_all()函数的扩展方法:
方法 | 说明 (这些方法的参数同find_all()参数都一样,就不具体一一说明了) |
---|---|
<>.find() | 搜索且只返回一个结果,字符串类型 |
<>.find_parents() | 在先辈结点中搜索,返回列表类型 |
<>.parent() | 在先辈结点中返回一个结果,字符串类型 |
<>.find_next_siblings() | 在后续平行结点中搜索,返回列表类型 |
<>._next_sibling() | 在后续平行节点中返回一个结果,字符串类型 |
<>.fingd_previous_siblings() | 在前序平行结点汇总搜索,返回列表类型 |
<>.find_previous——sibling() | 在前序平行节点中返回一个结果,字符串类型 |
3.4 实例五★:博客内容爬虫
1.功能描述:
输入:某篇博客的url链接python学习——对象和类
输出:博客内和python相关的链接整理输出
所用到库:requests、bs4
这是一个定向爬虫(只针对url爬取,不扩展爬取)。
(注意这里仅说明静态网页的爬取)
2.前期准备:
(1). 先看看想要爬取的信息是否都写在了该页面的html中,如果是,则可以使用定向爬虫爬取(因为有的是动态网页,现在先不学这些深一点的)
(2). 查看robots协议(robots.txt,如果没有就说明对爬虫没有限制)
3.程序设计:
(1). 从网络上获取博客页面html
(2). 提取信息,并放到合适的数据结构中★
(3).利用数据结构展示并输出结果
import requests
from bs4 import BeautifulSoup
import re
#访问伪装
hd = {'user-agent':'Chrome/10'}
url = "https://www.cnblogs.com/rdisheng/p/14978348.html"
data_dict = {}
#得到网页
def get_html(url,hd):
try:
r = requests.get(url,headers = hd,timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print("爬取失败")
#★解析提取★
def fill_list():
global data_dict
soup = BeautifulSoup(get_html(url,hd),'html.parser')#创建beautiful soup对象
#提取
#print(soup.find_all(string = re.compile("python")))
for tag_p in soup.find_all(name = 'a',string = re.compile("python")):
#print(tag_p)
tag_addr = tag_p.attrs
#print("tag_addr的类型",type(tag_addr))
tag_name = tag_p.string
data_dict[tag_name] = tag_addr['href']
#数据输出
def print_data():
for key in data_dict.keys():
print(key,":",data_dict[key])
def main():
fill_list()
print_data()
main()
成功输出!