发起请求
url:设置目标url
data=None:默认为None,标识发起的是一个get请求,反之,不为None,表示发起的是一个post请求
timeout:设置请求的超时时间(s)
cafile=None,设置证书文件(一般不用)
capath=None,设置证书文件路径(一般不用)
context=None,一般设置为一个ssl的对象(ssl._create_unverified_context())
忽略未认证CA证书(如果出现了ssl证书错误)
文件读写模式:
r:打开一个文件,只有可读权限
rb:以二进制的形式打开一个文件,有可读权限
r+:打开一个文件,有读写权限
rb+:以二进制形式打开一个文件,有读写权限
w:打开一个文件,有写入的权限
wb:以二进制的形式打开一个文件,有写入权限
w+:打开一个文件,有读写权限
wb+:以二进制形式打开一个文件,有读写权限
a:打开一个文件,有追加的权限
ab:以二进制的形式打开一个文件,有追加权限
a+:打开一个文件,有读写权限
ab+:以二进制形式打开一个文件,有读写权限
获取响应的状态码:
XXXX.status
获取响应的响应头:
XXXX.getheaders()
获取某一个响应头参数
XXXX.getheader('参数名')
获取当前请求的url地址
XXXX.url
获取请求的reason(如果成功返回的是OK)
XXXX.reason
注意:response.read()只可以读取一次
headers={}:设置请求头,传递一个字段类型的参数
我们首先了解一下 Urllib 库,它是 Python 内置的 HTTP 请求库,也就是说我们不需要额外安装即可使用,它包含四个模块:
- 第一个模块 request,它是最基本的 HTTP 请求模块,我们可以用它来模拟发送一请求,就像在浏览器里输入网址然后敲击回车一样,只需要给库方法传入 URL 还有额外的参数,就可以模拟实现这个过程了。
- 第二个 error 模块即异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后进行重试或其他操作保证程序不会意外终止。
- 第三个 parse 模块是一个工具模块,提供了许多 URL 处理方法,比如拆分、解析、合并等等的方法。
- 第四个模块是 robotparser,主要是用来识别网站的 robots.txt 文件,然后判断哪些网站可以爬,哪些网站不可以爬的,其实用的比较少。
在这里重点对前三个模块进行下讲解。
一、发送请求
使用 Urllib 的 request 模块我们可以方便地实现 Request 的发送并得到 Response
-
1、urlopen()
urllib.request 模块提供了最基本的构造 HTTP 请求的方法,利用它可以模拟浏览器的一个请求发起过程,同时它还带有处理authenticaton(授权验证),redirections(重定向),cookies(浏览器Cookies)以及其它内容。
我们来感受一下它的强大之处,以 Python 官网为例,我们来把这个网页抓下来:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
运行结果如下:
接下来我们看下它返回的到底是什么,利用 type() 方法输出 Response 的类型。
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(type(response))
输出结果如下:
<class 'http.client.HTTPResponse'>
通过输出结果可以发现它是一个 HTTPResposne 类型的对象,它主要包含的方法有 read()、readinto()、getheader(name)、getheaders()、fileno() 等方法和 msg、version、status、reason、debuglevel、closed 等属性。
得到这个对象之后,我们把它赋值为 response 变量,然后就可以调用这些方法和属性,得到返回结果的一系列信息了。+
例如调用 read() 方法可以得到返回的网页内容,调用 status 属性就可以得到返回结果的状态码,如 200 代表请求成功,404 代表网页未找到等。
下面再来一个实例感受一下:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))
运行结果如下:
200
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('Age', '2473'), ('Connection', 'close'), ('X-Served-By', 'cache-lcy1125-LCY'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '23'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
nginx
可见,三个输出分别输出了响应的状态码,响应的头信息,以及通过调用 getheader() 方法并传递一个参数 Server 获取了 headers 中的 Server 值,结果是 nginx,意思就是服务器是 nginx 搭建的。
利用以上最基本的 urlopen() 方法,我们可以完成最基本的简单网页的 GET 请求抓取。
如果我们想给链接传递一些参数该怎么实现呢?我们首先看一下 urlopen() 函数的API:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
可以发现除了第一个参数可以传递 URL 之外,我们还可以传递其它的内容,比如 data(附加数据)、timeout(超时时间)等等。
下面我们详细说明下这几个参数的用法:
-
data参数
data 参数是可选的,如果要添加 data,它要是字节流编码格式的内容,即 bytes 类型,通过 bytes() 方法可以进行转化,另外如果传递了这个 data 参数,它的请求方式就不再是 GET 方式请求,而是 POST。
下面用一个实例来感受一下:
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())
在这里我们传递了一个参数 word,值是 hello。它需要被转码成bytes(字节流)类型。其中转字节流采用了 bytes() 方法,第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode() 方法来将参数字典转化为字符串。第二个参数指定编码格式,在这里指定为 utf8。
-
timeout参数
timeout 参数可以设置超时时间,单位为秒,意思就是如果请求超出了设置的这个时间还没有得到响应,就会抛出异常,如果不指定,就会使用全局默认时间。它支持 HTTP、HTTPS、FTP 请求。
因此我们可以通过设置这个超时时间来控制一个网页如果长时间未响应就跳过它的抓取,利用 try except 语句就可以实现这样的操作,代码如下:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
-
其他参数
还有 context 参数,它必须是 ssl.SSLContext 类型,用来指定 SSL 设置。
cafile 和 capath 两个参数是指定 CA 证书和它的路径,这个在请求 HTTPS 链接时会有用。
cadefault 参数现在已经弃用了,默认为 False。
以上讲解了 urlopen() 方法的用法,通过这个最基本的函数可以完成简单的请求和网页抓取,如需更加详细了解,可以参见官方文档:https://docs.python.org/3/library/urllib.request.html。
-
2、Request
由上我们知道利用 urlopen() 方法可以实现最基本请求的发起,但这几个简单的参数并不足以构建一个完整的请求,如果请求中需要加入 Headers 等信息,我们就可以利用更强大的 Request 类来构建一个请求。
首先我们用一个实例来感受一下 Request 的用法:
mport urllib.request
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
可以发现,我们依然是用 urlopen() 方法来发送这个请求,只不过这次 urlopen() 方法的参数不再是一个 URL,而是一个 Request 类型的对象,通过构造这个这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可配置参数更加丰富和灵活。
下面我们看一下 Request 都可以通过怎样的参数来构造,它的构造方法如下:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
第一个 url 参数是请求 URL,这个是必传参数,其他的都是可选参数。
第二个 data 参数如果要传必须传 bytes(字节流)类型的,如果是一个字典,可以先用 urllib.parse 模块里的 urlencode() 编码。
第三个 headers 参数是一个字典,这个就是 Request Headers 了,你可以在构造 Request 时通过 headers 参数直接构造,也可以通过调用 Request 实例的 add_header() 方法来添加, Request Headers 最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。
第四个 origin_req_host 参数指的是请求方的 host 名称或者 IP 地址。
第五个 unverifiable 参数指的是这个请求是否是无法验证的,默认是False。意思就是说用户没有足够权限来选择接收这个请求的结果。例如我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。
第六个 method 参数是一个字符串,它用来指示请求使用的方法,比如GET,POST,PUT等等。
写个例子:
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {
'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
在这里我们通过四个参数构造了一个 Request,url 即请求 URL,在headers 中指定了 User-Agent 和 Host,传递的参数 data 用了 urlencode() 和 bytes() 方法来转成字节流,另外指定了请求方式为 POST。
通过观察结果可以发现,我们成功设置了 data,headers 以及 method。
另外 headers 也可以用 add_header() 方法来添加。
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
如此一来,我们就可以更加方便地构造一个 Request,实现请求的发送啦。
二、 处理异常
我们了解了 Request 的发送过程,但是在网络情况不好的情况下,出现了异常怎么办呢?这时如果我们不处理这些异常,程序很可能报错而终止运行,所以异常处理还是十分有必要的。+
Urllib 的 error 模块定义了由 request 模块产生的异常。如果出现了问题,request 模块便会抛出 error 模块中定义的异常。
主要有这两个处理异常类,URLError
,HTTPError
下面写个例子:
from urllib import request, error
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.URLError as e:
print(e.reason)
我们打开一个不存在的页面,照理来说应该会报错,但是这时我们捕获了 URLError 这个异常,运行结果如下:
Not Found
from urllib import request,error
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, seq='\n')
运行结果:
Not Found
404
Server: nginx/1.4.6 (Ubuntu)
Date: Wed, 03 Aug 2016 08:54:22 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Vary: Cookie
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Link: <http://cuiqingcai.com/wp-json/>; rel="https://api.w.org/"
HTTPError,它有三个属性。
- code,返回 HTTP Status Code,即状态码,比如 404 网页不存在,500 服务器内部错误等等。
- reason,同父类一样,返回错误的原因。
- headers,返回 Request Headers。
因为 URLError 是 HTTPError 的父类,所以我们可以先选择捕获子类的错误,再去捕获父类的错误,所以上述代码更好的写法如下:
from urllib import request, error
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
print(e.reason)
else:
print('Request Successfully')
这样我们就可以做到先捕获 HTTPError,获取它的错误状态码、原因、Headers 等详细信息。如果非 HTTPError,再捕获 URLError 异常,输出错误原因。最后用 else 来处理正常的逻辑,这是一个较好的异常处理写法。
requests
requests是基于urllib的再一次封装,具有urllib的一切特性,并且API调用更加方便,一个基于网络请求的模块,模拟浏览器发起请求
为什么使用requests模块?
1.自动处理url编码
2.自动处理post请求参数
3.简化cookie和代理的操作
cookie的操作:
a.创建一个cookiejar对象
b.创建一个handler对象
c.创建一个opener对象
代理的操作:
a.创建handler对象,代理ip和端口封装到该对象
b.创建opener对象
安装:pip3 install requests
使用流程:
1.指定url
2.使用requests模块发起请求
3.获取响应的二进制数据
4.进行持久化存储
requests包括五中请求:get,post,ajax的get请求,ajax的post请求,综合
import requests
1、指定url
url = 'https://www.sogou.com/'
2、发起get请求:get方法会返回请求成功的相应对象
response = requests.get(url=url)
3、获取响应中的数据值:text可以获取响应对象中字符串形式的页面数据
page_data = response.text
print(page_data)
4、持久化操作
with open('./sougou.html','w',encoding='utf-8') as fp:
fp.write(page_data)
requests对象中其他重要属性:
1.text 可以获取响应对象中字符串形式的页面数据
2.content 获取的是response对象中二进制(byte)类型的页面数据
3.response.status 返回一个响应状态码
4.response.headers 返回响应头信息
自定义请求头信息:
from fake_useragent import UserAgent
定制请求头
headers ={
"User-Agent":UserAgent().random
}封装get请求参数:
params = {
"变量名":"参数"
}使用requests发起一个post请求
import requests
from fake_useragent import UserAgent
def login():
1、指定POST请求的URL
url = "https://accounts.douban.com/j/mobile/login/basic"
封装post请求的参数
data = {
"ck": "",
"name": "16619983290",
"password": "wwy123456",
"remember": "false",
"ticket": ""
}
headers = {
"User-Agent": UserAgent().random
}
2、发起POST请求
response = requests.post(url=url, data=data, headers=headers)
3、获取响应回来的信息
print(response.text)
requests库中的session作用:
维持统一会话,在跨请求访问的时候能够保存一些信息(比如cookies)
cookie:
1.基于用户的用户数据
2.需求:爬取xx用户的豆瓣网的个人主页数据
cookie作用:服务端使用cookie来记录客户端的状态信息
实现流程:
1.执行登录操作(获取cookie)
2.在发起个人主页请求时,需要将cookie携带到该请求中
注意:session对象:发送请求(会将cookie对象进行自动存储)
正则
单字符匹配
. 除换行符之外的任意字符
\d 表示数字
\D 匹配非数字
\w 匹配单词字符[a-z,A-Z,0-9]
\W 匹配非单词字符
\s 匹配空白字符,空格,\n \t....
\S 匹配非空白字符
^ 匹配以...开头
$ 匹配以...结尾
[0-9] => \d 匹配0-9
多字符匹配(贪婪匹配)
匹配*前面的字符任意次数
匹配+前面的字符至少一次
?匹配?前面的字符0-1次
{n,m}匹配{n,m}前面的字符n-m次
多字符匹配(非贪婪匹配)
*?
+?
??
其他
()分组
|逻辑或
\转义字符
re模块下的方法
re.compile():构建正则表达式对象
re.match():从起始位开始匹配,单次匹配,如果* 匹配到结果立即返回,反之,返回None
re.search():在整个字符串中进行匹配,单次匹配,如果匹配到结果立即返回,反之,返回None
re.findall():匹配出整个字符串中,所有符合正则规则的结果,返回一个列表
re.finditer():匹配出整个字符串中,所有符合正则规则的结果,返回的是一个可迭代对象
re.sub():根据正则表达式进行字符串替换
re.split():根据正则表达式进行分割
xpath
安装:pip install lxml
引用:from lxml import etree
创建etree对象进行指定数据解析
1.本地
etree = etree.parse('本地路径')
etree.xpath('xpath表达式')
2.网络
etree = etree.HTML('网络请求到页面的数据')
etree.xpath('xpath表达式')
常用的xpath表达式:
1.属性定位:
找到class属性值为song的div标签
//div[@class='song']
2.层级索引定位
找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
//div[@class='tang']/ul/li[2]/a
3.逻辑运算
找到href属性值为空且class属性值为du的a标签
//a[@href='' and @class='du']
4.模糊匹配
/表示获取某个标签下的文本内容 //div[@class='song']/p[1]/text()
//表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class='tang']//text()
5.取属性
//div[@class='tang']//li[2]/a/@href
bs4(python独有简单便捷和高效)
环境安装:pip install lxml bs4用到lxml库,如果没有安装过lxml库的时候,需要安装一下
代码使用流程:
核心思想:可以将html文档可以转换成BeautifulSoup对象,调用该对象中的属性和方法进行
1.导包
from bs4 import BeautifulSoup
2.创建BeautifulSoup对象a.本地
Beautiful('open('本地的html文件')','lxml')
b.网络
Beautiful('网络请求到的页面数据','lxml')
属性和方法:
1.根据标签名查找
soup.a 只能找到第一个符合要求的标签
2.获取属性
soup.a.attrs 获取a所有的属性和属性值,返回一个字典
soup.a.attrs['href'] 获取href属性
soup.a['href'] 也可简写为这种形式
3.获取内容
soup.a.string /text()
soup.a.text //text()
soup.a.get_text() //text()
如果标签还是标签,那么string获取到的结果为none,而其他两个,可以获取文本内容
4.find:找到第一个符合要求的标签
soup.find('a') 找到第一个符合要求的
soup.find('a',title='xxx')
soup.find('a',alt='xxx')
soup.find('a',class='xxx')
soup.find('a',id='xxx')
5.find_All:找到所有符合要求的标签
soup.find_All('a')
soup.find_All(['a','b']) 找到所有的a和b标签
soup.find_All('a',limit=2) 限制前两个
6.根据选择器选择指定的内容
select:soup.select('#feng')
常见的选择器:
标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器
层级选择器:
div .dudu #lala .name .xixi 下面好多级 div//img
div > p > a > .lala 只能是下面一级 div/img
select选择器返回永远是列表,需要通过下标提 取指定对象