1.爬虫概述
- 网络爬虫(又称为网页蜘蛛,网络机器人,更经常的称为网页追逐者),它按照一定的规则自动地抓取网络信息。
- 爬虫可以使用Python或者PHP等多种语言,但是大多数人会选择Python来编写爬虫程序,因为Python不仅语法简单易上手,它还拥有庞大的开发者社区和规模巨大的模块库,是写爬虫程序的最好选择。
1.1 背景分类
1.2 基本流程
- 发送请求
通过HTTP库向目标网站发送一个请求,等待响应。
- 获取响应内容
服务器正常响应后能得到一个Response,内容为获取页面的内容,可能是HTML、Json字符或者二进制数据等类型。
- 解析内容
得到的内容如果是html,可以通过正则表达式或网页解析库进行解析,如果是json可以直接转换为json对象解析,如果是二进制数据可以保存后作进一步处理。
- 保存数据
把解析后的数据保存下来,可以是文本,也可以保存到数据库当中。
2.爬虫协议
Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,该协议属于一个规范,并不能保证网站的隐私。
Robots协议是国际互联网界通行的道德规范,基于以下原则:
- 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权。
- 网站有义务保证其使用者的个人信息和隐私不被侵犯。
使用爬虫的时候我们应当注意:
- 拒绝访问和抓取有关不良信息的网站。
- 注意版权意识,对于原创内容,未经允许不要将信息用于其他用途,特别是商业方面。
- 严格遵循robots.txt协议。
- 爬虫协议查看方式
大部分网站都会提供自己的robots.txt文件,这个文件会告诉我们该网站的爬取准则,查看方式是在域名加"/robots.txt"并回车。
https://www.baidu.com/robot
在上面我们可以看到,百度的爬虫协议中对不同的访问者有着不同的要求。
- User-agent为访问用户,出现多个User-agent表示该网站对不同的用户提供不同的准则。
- Disallow 表示不允许访问的目录
常使用的规范如下:
User-agent: * 这里的代表的所有的搜索引擎种类,是一个通配符。
Disallow: /baidu/ 这里定义是禁止爬寻baidu目录下面的目录。
Disallow: /dotcpp/.htm 禁止访问/dotcpp/目录下的所有以".htm"为后缀的URL(包含子目录)。
Disallow: /?* 禁止访问网站中所有包含问号 (?) 的网址。
Disallow: /.jpg$ 禁止抓取网页所有的.jpg格式的图片。
Disallow:/dotcpp/dotcpp.html 禁止爬取dotcpp文件夹下面的dotcpp.html文件。
Allow: /dotcpp/ 这里定义是允许爬寻dotcpp目录下面的目录。
Allow: /dotcpp 这里定义是允许爬寻tmp的整个目录。
Allow: .htm$ 仅允许访问以".htm"为后缀的URL。
Allow: .gif$ 允许抓取网页和gif格式图片。
Sitemap: 网站地图,告诉爬虫这个页面是网站地图。
3.爬虫技术基础
3.1 网络请求
我们在使用爬虫的时候离不开URL地址和下载页面,首先我们就来了解一下URL。它的语法格式一般为:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
URL由三部分组成,第一部分是协议,有http、https、ftp等,
第二部分存放资源的服务器的域名或IP地址,
第三部分为资源的具体地址。
我们在进行网络请求的时候通常采用三种方式:urllib、urllib3和requests。
3.1.1 urllib模块
urllib是Python系统库中存在的一个模块,它提供了多个子模块:
- urllib.request
提供打开和阅读URL的方法和类
- urllib.error
包含异常类
- urllib.parse
解析和引用URL
- urllib.robotparser
解析robots.txt文件
我们使用最多的是第一个子模块中的方法,其中包含了对服务器请求的发出、跳转、代理等。 当我们向网页发送请求的时候,采用urllib.request.urlopen()方法。
import urllib.request#引入模块
response = urllib.request.urlopen('http://www.baidu.com/')
html = response.read().decode('utf-8')#以utf-8格式读取网页的内容
print(html)#输出内容
3.1.2 requests模块
requests是一种第三方模块,主要用于发送请求,它在使用的时候比urllib模块要简洁方便很多,我们可以在命令操作符里通过pip install requests来安装,也可以在Pycharm中直接进行安装。
import requests
r = requests.get('https://www.douban.com/')
print(r.status_code)#输出状态码
print(r.encoding)#输出编码格式
print(r.headers)#输出头部文件
print(r.cookies)#输出cookie信息
print(r.content)#输出字节流形式网页源码
3.2 请求headers处理
我们首先打开我们要访问的网站,然后根据浏览器的打开方式进入检查页面,例如谷歌浏览器可以直接按F12或者Ctrl+Shift+I,进入下要页面:
找到document文件并单击,如下图页面:
这个就为我们的头部信息,也就是我们当前访问此网站的用户信息,我们复制一下然后开始使用它,代码如下:
import requests
url = 'https://www.baidu.com/'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}#头部信息
r = requests.get(url,headers = headers)#发送网络请求
print(r.text)#以文本形式输出网页源码
这种方式类似于我们模拟了一个用户去访问该网站,等同于手动在打开浏览器去访问。
3.3 网络超时
我们在发送网络请求的时候避免不了超时现象的发生,如果我们不设置一个超时时间,那么系统会因为它长时间未响应而无法打开网页。
超时又分为连接超时和读取超时。连接超时就是在程序默认的等待时间内没有得到服务器的响应。
import datetime
import requests
url = 'http://www.google.com.hk'
print(datetime.datetime.now())
try:
r = requests.get(url, timeout = 5).text
print('获得响应')
except requests.exceptions.RequestException as s:
print('连接超时')
print(s)
print(datetime.datetime.now())
由于国内的服务器无法正常访问谷歌,所以出现连接超时的现象.
我们在这条语句中设置超时限定时间。
r = requests.get(url, timeout = 5).text
我们再来看一下读取超时,读取超时就是指客户端等待服务器发送请求的时间,也就是指连接成功之后服务器返回响应之间等待的时间,为了防止服务器响应速度过慢而导致等待时间过长,我们可以设置读取时间,超过了规定时间即为读取超时,它的用法和连接超时类似,关键语句为:
r = requests.get(url, timeout=(5,10)).text
在这里15即为读取限制时间,如果超过即为读取超时。
4.正则表达式
4.1 元字符
正则表达式的结构由普通字符和元字符组成,普通字符就是我们日常使用的文本字符,而元字符有它特定的含义,具有匹配信息的功能。
常用的正则表达式元字符如下表格:
- 字符类[]
例如在一个列表中有‘apython’、‘bpython’、‘cpython’、‘dotcpp’四个元素,我们使用‘[abc]python’可以直接匹配到前面三个元素。
- [a-zA-Z0-9]
这个可以匹配一个任意大小写字母或者数字,等同于\w。
- 重复 {}
如果我们使用{n},即‘pyt{}on’,只能匹配到python,使用{n,}可以匹配至少n次,使用{n,m}可以匹配至少n次,最多m次。
- 开始于结束字符
使用‘^’和‘$’可以匹配一行字符串的开始和结束。
- a|b
可以匹配a或者b。
- 匹配出现次数
4.2 re模块
re模块用于实现正则表达的操作,它里面提供了很多方法,例如search()、match()、findall()、split()、sub()等,下面我们依次进行学习。
4.2.1 search()方法
search方法在输入的字符串中查找,返回第一个匹配的内容,在找到之后返回match对象,未找到返回None。
语法格式如下:
re.search(pattern,string,[flags])
pattern表示需要匹配的模式,即以正则表达式来转换string字符串,flags为标志位,用于控制匹配方式。
Flags常用参数如下:
- A
对于\w、\b、\d等只进行ASCII匹配- I
匹配的时候不区分大小写- S
使用‘.’字符匹配所有字符- X
忽略字符串中为转移的空格及注释
import re
string = 'this is www.DOTcpp.com'
pattern = r'dotcpp'
print(re.search(pattern,string,re.I))#不区分大小写的方式匹配
这个例子中我们以不区分大小写的方式在string字符串中搜索‘dotcpp’,输出的信息为它的位置及搜索信息。
4.2.2 match()方法
match方法从字符串的开始位置查询匹配内容,如果找到则返回一个match对象,未找到返回None。
语法格式如下:
re.match(pattern,string,[flags])
import re
string = 'this is www.DOTcpp.com'
pattern = r'dotcpp'
print(re.match(pattern,string,re.I))#不区分大小写的方式匹配
import re
string = 'this is www.DOTcpp.com'
pattern = r'Th'
print(re.match(pattern,string,re.I))#不区分大小写的方式匹配
要注意search用法和match用法的区别,一般推荐大家使用research方法。
4.2.3 findall()方法
findall方法会在整个字符串中搜索出符合条件的字符串,然后以列表的形式返回。
语法格式如下:
re.findall(pattern,string,[flags])
import re
string = 'this is www.DOTcpp.com dotcpp'
pattern = r'dotcpp'
print(re.findall(pattern,string,re.I))#不区分大小写的方式匹配
这种方式就匹配到了string字符串中所有符合pattern条件的内容,然后存放在一个列表中,也可以通过遍历的方式输出这些内容
4.2.4 split()方法
split()方法又被称作分割方法,它能够以正则表达式的相关格式分割字符串,并返回列表,它的语法格式如下:
re.split(pattern,string,[maxsplit],[flags])
这里多了一个maxsplit参数,用于表示最多的拆分次数,其余参数与上面一致。
4.2.5 sub()方法
Sub方法可以替换字符串中的内容,语法格式如下:
re.sub(pattern,repl,string,count,[flags])
repl为替换的字符串,count为替换的最大次数,默认为0,其余参数与上面一致。
import restring = '号码为:18812345678'
#假定手机号为18812345678pattern = r'\d'print(re.sub(pattern,'x',
string))
5.BeautifulSoup
BeautifulSoup4是爬虫必学的技能。BeautifulSoup最主要的功能是从网页抓取数据,Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。BeautifulSoup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐使用lxml 解析器。
5.1 介绍
BeautifulSoup4和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
BeautifulSoup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐使用lxml 解析器。
Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。
5.2 使用
假设有这样一个 [aa.Html],具体内容如下:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta content="always" name="referrer" />
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css" />
<title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div id="u1">
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻 </a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123 </a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图 </a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频 </a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧 </a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品 </a>
</div>
</div>
</div>
</div>
</body>
</html>
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser") # 解析器
print(bs.prettify()) # 格式化html结构
print(bs.title) # 获取title标签的名称
print(bs.title.name) # 获取title的name
print(bs.title.string) # 获取head标签的所有内容
print(bs.head)
print(bs.div) # 获取第一个div标签中的所有内容
print(bs.div["id"]) # 获取第一个div标签的id的值
print(bs.a)
print(bs.find_all("a")) # 获取所有的a标签
print(bs.find(id="u1")) # 获取id="u1"
for item in bs.find_all("a"):
print(item.get("href")) # 获取所有的a标签,并遍历打印a标签中的href的值
for item in bs.find_all("a"):
print(item.get_text())
部分截图:
5.2 四个对象
5.2.1 Tag
对应的就是HTML中的标签:
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# 获取title标签的所有内容
print(bs.title)
# 获取head标签的所有内容
print(bs.head)
# 获取第一个a标签的所有内容
print(bs.a)
# 类型
print(type(bs.a))
他们都属于标签信息,我们可以通过标签名来获取到这些标签中的内容,有一点需要注意的是在查找的时候,对应的是符合要求的第一个标签,
Tag有两个属性,分别是name和attrs,name也就是标签的名字,attrs对应class、id等信息。
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# [document] #bs 对象本身比较特殊,它的 name 即为 [document]
print(bs.name)
# head #对于其他内部标签,输出的值便为标签本身的名称
print(bs.head.name)
# 在这里,我们把 a 标签的所有属性打印输出了出来,得到的类型是一个字典。
print(bs.a.attrs)
#还可以利用get方法,传入属性的名称,二者是等价的
print(bs.a['class']) # 等价 bs.a.get('class')
# 可以对这些属性和内容等等进行修改
bs.a['class'] = "newClass"
print(bs.a)
# 还可以对这个属性进行删除
del bs.a['class']
del bs.a['name']
print(bs.a)
5.2.2 NavigableSting
既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如:
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
print(bs.title.string)
print(type(bs.title.string))
5.2.3 BeautifulSoup
BeautifulSoup对象对应的是文档中的内容,它类似于一个特殊的标签,我们可以获取到它的类型、名称和属性,也就是上面我们所使用到的:
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(type(bs.name))
print(bs.name)
print(bs.attrs)
5.2.4 Comment
Comment对象是一个特殊的NavigableSting对象,它输出的内容没有注释符号,
如果不加以处理会影响我们对文档的解析,因为这种方式会忽略掉文档的注释, 因此注释中的内容会以代码格式被解析出来,进而影响我们的后续操作,所有我们 一般会采用.string来输出内容。
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
print(bs.a)
# 此时不能出现空格和换行符,a标签如下:
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新闻--></a>
print(bs.a.string) # 新闻
print(type(bs.a.string)) # <class 'bs4.element.Comment'>
5.3 遍历文档树
5.3.1 .contents
获取Tag的所有子节点,返回一个list。
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# tag的.content 属性可以将tag的子节点以列表的方式输出
print(bs.head.contents)
# 用列表索引来获取它的某一个元素
print(bs.head.contents[1])
5.3.2 .children
获取Tag的所有子节点,返回一个生成器。
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
for child in bs.body.children:
print(child)
5.4 搜素文档树
5.4.1 find_all(name, attrs, recursive, text, **kwargs)
name参数即tag的名字,attrs为类或id,recursive为递归性,text为文本参数。
- name
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
def name_is_exists(tag):
return tag.has_attr("name")
s = bs.find_all(name_is_exists)
for i in s:
print(i)
- kwargs参数
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# 查询id=head的Tag
t_list = bs.find_all(id="head")
print(t_list)
# 查询href属性包含ss1.bdstatic.com的Tag
t_list = bs.find_all(href=re.compile("http://news.baidu.com"))
print(t_list)
# 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
t_list = bs.find_all(class_=True)
for item in t_list:
print(item)
- attrs参数
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# 查询id=head的Tag
t_list = bs.find_all(id="head")
print(t_list)
# 查询href属性包含ss1.bdstatic.com的Tag
t_list = bs.find_all(href=re.compile("http://news.baidu.com"))
print(t_list)
t_list = bs.find_all(attrs={"data-foo": "value"})
for item in t_list:
print(item)
- text参数
通过text参数可以搜索文档中的字符串内容,与name参数的可选值一样,text参数接受 字符串,正则表达式,列表
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.find_all(attrs={"data-foo": "value"})
for item in t_list:
print(item)
t_list = bs.find_all(text="hao123")
for item in t_list:
print(item)
t_list = bs.find_all(text=["hao123", "地图", "贴吧"])
for item in t_list:
print(item)
t_list = bs.find_all(text=re.compile("\d"))
for item in t_list:
print(item)
- limit参数
可以传入一个limit参数来限制返回的数量,当搜索出的数据量为5,而设置了limit=2时,此时只会返回前2个数据
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.find_all("a",limit=3)
for item in t_list:
print(item)
5.4.1.1正则表达式过滤
如果传入的是正则表达式,那么BeautifulSoup4会通过search()来匹配内容
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
t_list = bs.find_all(re.compile("a"))
for item in t_list:
print(item)
列表:如果传入一个列表,BeautifulSoup4将会与列表中的任一元素匹配到的节点返回
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
t_list = bs.find_all(["meta","link"])
for item in t_list:
print(item)
方法:传入一个方法,根据方法来匹配
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
def name_is_exists(tag):
return tag.has_attr("name")
t_list = bs.find_all(name_is_exists)
for item in t_list:
print(item)
5.4.2 find()
find()将返回符合条件的第一个Tag,有时我们只需要或一个Tag时,我们就可以用到find()方法了。当然了,也可以使用find_all()方法,传入一个limit=1,然后再取出第一个值也是可以的,不过未免繁琐。
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
# 返回只有一个结果的列表
t_list = bs.find_all("title",limit=1)
print(t_list)
# 返回唯一值
t = bs.find("title")
print(t)
# 如果没有找到,则返回None
t = bs.find("abc")
print(t)
从结果可以看出find_all,尽管传入了limit=1,但是返回值仍然为一个列表,当我们只需要取一个值时,远不如find方法方便。但是如果未搜索到值时,将返回一个None.
在上面介绍BeautifulSoup4的时候,我们知道可以通过bs.div来获取第一个div标签,如果我们需要获取第一个div下的第一个div,我们可以这样:
5.5 CSS选择器
BeautifulSoup支持发部分的CSS选择器,在Tag获取BeautifulSoup对象的.select()方法中传入字符串参数,即可使用CSS选择器的语法找到Tag:
5.5.1 通过标签名查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.select('title'))
print(bs.select('a'))
5.5.2 通过类名查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.select('.mnav'))
5.5.3 通过id查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.select('#u1'))
5.5.4 组合查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.select('div .bri'))
5.5.5 属性查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
print(bs.select('a[class="bri"]'))
print(bs.select('a[href="http://tieba.baidu.com"]'))
5.5.6 直接子标签查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.select("head > title")
print(t_list)
5.5.7 兄弟节点标签查找
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.select(".mnav ~ .bri")
print(t_list)
5.5.8 获取内容
from bs4 import BeautifulSoup
import re
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")
t_list = bs.select("title")
print(bs.select('title')[0].get_text())
6.实战-爬取天气信息
import requests
from bs4 import BeautifulSoup
qy = open('C:/Users/轻烟/Desktop/db.txt',mode='a',encoding='utf-8')
res = requests.get('http://www.weather.com.cn/weather15d/101190401.shtml')
res.encoding = 'utf-8'
html = res.text
soup = BeautifulSoup(html,'html.parser')#解析文档
weathers = soup.find(id="15d",class_="c15d").find('ul',class_="t clearfix").find_all('li')
for weather in weathers:
weather_date = weather.find('span',class_="time")
weather_wea = weather.find('span',class_="wea")
weather_tem = weather.find('span',class_="tem")
weather_wind = weather.find('span',class_="wind")
weather_wind1 = weather.find('span',class_="wind1")
result = '日期:'+weather_date.text,'天气:'+weather_wea.text,'温度:'+weather_tem.text,'风力:'+weather_wind.text+weather_wind1.text
print(result)#输出
print(result,file = qy)#保存到文档中