爬虫学习(2)

第三章主要是介绍并使用了一些写爬虫过程中使用到的基本库。
最基础的HTTP库有urllib、httplib2、requests、treq等。
接下来我们就学习一下几个常用的库并在最后通过一个实例来应用。

urllib

python3中,将urllib和urllib2统一为urllib。
urllib是python内置的HTTP请求库,包含如下四个模块:

  • request: 最基本的HTTP请求模块,用来模拟发送请求
  • error:异常处理模块
  • parse:工具模块,提供了许多URL处理方法,比如拆分、解析、合并等
  • robotparser:识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些不能爬,用的不多
    下面重点介绍前三个模块

    发送请求

    urlopen()

    urllib.request模块提供了最基本的构造HTTP请求的方法,除了模拟浏览器发起请求,还可以处理授权验证authenticati、重定向redirection、浏览器Cookies以及其他内容。
    1
    2
    3
    import urllib.request
    response = urllib.request.urlopen('https://www.baidu.com/')
    print(response.read().decode('utf-8'))

运行结果如下:

1
2
3
4
5
6
7
8
9
10
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

用type(response)可以得到返回的是

'http.client.HTTPResponse'>```类型的对象,它包含了很多属性和方法,供我们调用。例如,read()方法可以得到返回的网页内容,status属性可以得到返回结果的状态码(200表示请求成功,404代表网页未找到);getheaders()可以返回响应头信息;getheader(name)可以获取响应头中name的值。
1
2
3
4

下面看一下urlopen()的API
```python
urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False, context=None)

  • data:可选参数,如果要使用该参数,要用bytes()方法将参数转化为字节流编码格式的内容,即bytes类型;若使用了该参数,那么请求方式是POST方式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import urllib.request
    import urllib.parse
    data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')
    response = urllib.request.urlopen('https://httpbin.org/post',data=data)
    print(response.read())
    # {
    # "args": {},
    # "data": "",
    # "files": {},
    # "form": {
    # "word": "hello"
    # },
    # "headers": {
    # "Accept-Encoding": "identity",
    # "Connection": "close",
    # "Content-Length": "10",
    # "Content-Type": "application/x-www-form-urlencoded",
    # "Host": "httpbin.org",
    # "User-Agent": "Python-urllib/3.6"
    # },
    # "json": null,
    # "origin": "210.12.101.154",
    # "url": "https://httpbin.org/post"
    # }

传递一个参数word,其值为hello,它需要被转化成byte类型。采用bytes()方法进行转换,该方法的第一个参数是str类型,用urllib.parse模块的urlencode()方法将参数字典转化成字符串;第二个参数是指定编码格式。
我们请求的站点可以提供HTTP请求测试,用来测试POST请求,可以输出我们的请求信息,其中就有我们传递的data参数。
data参数出现在form中,表明是模拟了表单提交的方式,以POST方式传输数据。

  • timeout:用于设置超时时间,单位为秒,如果请求超出设置时间还没得到响应,就会抛出异常。若不指定该参数,则使用全局默认时间。

    1
    2
    3
    4
    5
    6
    7
    8
    import urllib.request
    import socket
    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):#判断异常是socket.timout(超时异常)
    print('timeout')
  • context:指定SSL设置,必须是ssl.SSLContext类型

  • cafile:指定CA证书,请求HTTPS链接时有用
  • capath:指定证书路径,请求HTTPS链接时有用
  • cadefault:已经弃用了,默认为False

Request

urlopen()可以实现最基本请求的发起,如果请求中需要加入Header等信息,可以利用Request类来构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import urllib.request
request = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
# <html>
# <head>
# <script>
# location.replace(location.href.replace("https://","http://"));
# </script>
# </head>
# <body>
# <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
# </body>
# </html>

从上述代码可以看出,urlopen的参数是一个Request类型的对象。通过构造这个数据结构,一方面可以将请求独立成一个对象,另一方面可以丰富和灵活地配置参数。

下面我们来看一下Request的构造方法:

1
2
class urillib.request.Request(self, url, data=None, headers={}, origin_req_host=None, unverifiabl
e=False, method=None)

  • url:必传参数,请求URL
  • data:必须传bytes类型的,如果是字典,可以用urllib.parse.urlencode()进行编码
  • headers:是一个字典,就是请求头,构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加

    添加请求头最常用的方法是修改User-Agent来伪装浏览器,默认的是Python-urllib。例如,伪装成火狐,可以修改为:
    Mozilla/5.0 (X11;U;Linux i868) Gecko/20071127 Firefox/2.0.0.11

  • origin_req_host:指请求方的host名称或ip地址

  • unverifiable:表示这个请求是否是无法认证的,true表示用户没有权限来选择接收这个请求的结果。默认为False。
  • method:str,指示请求使用的方法,GET/POST/PUT等
    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
    import urllib.request
    import urllib.parse
    url = 'http://httpbin.org/post'
    headers = {
    'User-Agent':'Mozilla/4.0(compatible;MISE 5.5;Windows NT)',
    'Host':'httpbin.org'
    }
    dict = {
    'name':'Germey'
    }
    data = bytes(urllib.parse.urlencode(dict),encoding='utf-8')
    req = urllib.request.Request(url,data=data,headers = headers,method='POST')
    response = urllib.request.urlopen(req)
    print(response.read().decode('utf-8'))
    #{
    # "args": {},
    # "data": "",
    # "files": {},
    # "form": {
    # "name": "Germey"
    # },
    # "headers": {
    # "Accept-Encoding": "identity",
    # "Connection": "close",
    # "Content-Length": "11",
    # "Content-Type": "application/x-www-form-urlencoded",
    # "Host": "httpbin.org",
    # "User-Agent": "Mozilla/4.0(compatible;MISE 5.5;Windows NT)"
    # },
    # "json": null,
    # "origin": "210.12.101.154",
    # "url": "http://httpbin.org/post"
    # }

可以看到,我们成功设置了data、method、headers
另外,之前我们提到headers可以用add_headers()来添加:

1
2
req = request.Request(url,data,method='POST')
req.add_header('User-Agent','Mozilla/4.0(compatible;MISE 5.5;Windows NT)')

高级用法

Handler,可以认为是各种处理器,比如处理登录验证、处理Cookies、处理代理设置。
urllib.request.BaseHandler类,是所有其他Handler的父类,提供了最基本的方法,然后各种Handler子类继承该类。
下面具体看一下常用的几个用法:
1.验证
有时候我们会遇到图中情况,提示输入用户名和密码,验证成功后才能查看页面

如果请求这种页面,借助HTTPBasicAuthHandler来处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.request import URLError
username = 'username'
password = 'password'
url = 'http://localhost:5000/'
p = HTTPPasswordMgrWithDefaultRealm() #创建一个密码管理对象
#添加账户信息,第一个参数realm是与远程服务器相关的域信息,一般写None,后面三个参数分别是 Web服务器、用户名、密码
p.add_password(None,url,username,password)
#构建HTTPBasicAuthHandler对象,参数是创建的密码管理对象
auth_handler = HTTPBasicAuthHandler(p)
#创建自定义opener对象
opener = build_opener(auth_handler) #urlopen就是一个Opener,配置Opener可以实现更高级的功能

try:
result = opener.open(url)
html = result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)

实例化HTTPBasicAuthHandler对象,其参数是HTTPPasswordMgrWithDefaultRealm对象,利用add_password()添加用户名和密码,这样就建立了一个处理验证的Handler。
2.代理
做爬虫时添加代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from urllib.error import URLError
from urllib.request import ProxyHandler,build_opener

proxy_handler = ProxyHandler({
#代理列表,键名是协议类型,value是代理连接,可以添加多个代理
'http':'http://127.0.0.1:9743',#在本地搭建代理,运行在9743端口
'https':'https://127.0.0.1:9743'
})
opener = build_opener(proxy_handler)
#构造完代理handler后,发送请求
try:
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)

3.Cookies
首先看一下如何获取网站的Cookies:

1
2
3
4
5
6
7
8
import http.cookiejar,urllib.request

cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+'='+item.value)

会输出每条Cookie的名称和值。
有时候,需要我们将Cookies存为文件:

1
2
3
4
5
6
7
import http.cookiejar,urllib.request
filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handle = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handle)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True,ignore_expires=True)

MozillaCookieJar是CookieJar的子类,可以用来处理Cookies和文件相关的事件,比如读取和保存Cookies,可以将Cookies保存成Mozilla型浏览器的Cookies格式。
另外,LWPCookieJar也是类似的,保存的事libwww-perl(LWP)格式的Cookies文件。
下面看一下读取cookies文件的操作:

1
2
3
4
5
6
7
import http.cookiejar,urllib.request
cookie = http.cookiejar.MozillaCookieJar()
cookie.load('cookies.txt',ignore_discard=True,ignore_expires = True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

上面出现过的两个参数ignore_discard、ignore_expires,ignore_discard的意思是即使cookies将被丢弃也将它保存下来,ignore_expires的意思是如果cookies已经过期也将它保存并且文件已存在时将覆盖,在这里,我们将这两个全部设置为True。

处理异常

发送请求时,如果遇到异常不处理,程序可能会报错停止运行,所以有必要进行异常处理。
urllib的error模块定义了由request模块产生的异常。

URLError

由request模块产生的异常都可以通过捕获这个类来处理:

1
2
3
4
5
6
from urllib import request,error
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.URLError as e:
print(e.reason)
#Not Found

HTTPError

它是URLError的子类,专门处理HTTP请求错误,比如认证失败等,有code、reason、headers三个属性。

  • code: 返回HTTP状态码
  • reason:返回错误原因
  • headers:返回请求头
    1
    2
    3
    4
    5
    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')

因为HTTPError是URLError的子类,所以我们一般异常处理时都是先捕获子类异常,再是父类错误,所以优化后的代码如下:

1
2
3
4
5
6
7
8
9
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')

有时候,reason属性返回的不一定是字符串,可能是一个对象:

1
2
3
4
5
6
7
8
9
10
11
import socket
from urllib import request,error
try:
response = request.urlopen('https://www.baidu.com',timeout=0.01)
except error.URLError as e:
print(type(e.reason))
if isinstance(e.reason,socket.timeout):
print('TIME OUT')

# <class 'socket.timeout'>
# TIME OUT

解析链接

1.urlparse()
from urllib.parse import urlparse,可以实现URL的识别和分段。

1
2
3
4
urllib.parse.urlparse(urlstring,scheme='',allow_fragments=True)
# http://www.baidu.com/index.html;user?id=5#comment
# scheme:http ; netloc:www.baidu.com ; path:index.html ;
# params:user ; query: id=5 ; fragment:comment.

scheme是默认协议,如果url没有带协议信息,将scheme作为默认协议;
allow_fragments默认为True,是否忽略fragment;如果被设置为false,则忽略fragment部分,该部分被解析为path/parameters/query的一部分。

另外,返回结果是一个元组,可以用索引顺序来获取,也可以用属性名获取。
2.urlunparse()
长度必须是6

1
2
3
4
from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment',scheme='https')
print(result)
#http://www.baidu.com/index.html;user?id=5#comment

3.urlsplit()
和urlparse类似,只是不解析param部分

1
2
3
4
5
6
7
from urllib.parse import urlsplit
result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
print(result)
#SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
print(result.scheme,result[0],sep='\n')
#http
#http

4.urlunsplit()
与urlunparse类似,将链接的各个部分组合成完整链接。传入对象也是可迭代的,比如元组、列表等,唯一区别是长度为6

1
2
3
4
from urllib.parse import urlunsplit
data = ['http','www.baidu.com','index.html','a=6','comment']
print(urlunsplit(data))
#http://www.baidu.com/index.html?a=6#comment

5.urljoin()
也是一种生成连接的方法,基础链接base_url作为第一个参数,新的链接作为第二个参数,该方法分析base_url的scheme、netloc、path,并对新链接缺失部分进行填充,返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
from urllib.parse import urljoin
print(urljoin('http://www.baidu.com','FAQ.html'))
print(urljoin('http://www.baidu.com/about.html','https://cuiqingcai.com/FAQ.html'))
print(urljoin('http://www.baidu.com?wd=abc','https://cuiqingcai.com/index.php'))
print(urljoin('www.baidu.com','?category=2#comment'))
print(urljoin('www.baidu.com#comment','?category=2'))
# http://www.baidu.com/index.html?a=6#comment
# http://www.baidu.com/FAQ.html
# https://cuiqingcai.com/FAQ.html
# https://cuiqingcai.com/index.php
# www.baidu.com?category=2#comment
# www.baidu.com?category=2

如果新的链接也就是第二个参数不存在scheme、netloc、path,则用base_url中的scheme、netloc、path进行填充,简单的说就是以新的链接为主,如果缺少什么,就从base_url中进行补充。
6.urlencode()
在构造GET请求时非常有效

1
2
3
4
5
from urllib.parse import urlencode
params = {'name':'Alice','age':'1'}
base_url = 'http://www.baidu.com?'
print(base_url+urlencode(params))
#http://www.baidu.com?name=Alice&age=1

首先用字典将参数表示出来,然后用urlencode()将其序列化为GET请求参数
7.parse_qs()

1
2
3
4
from urllib.parse import parse_qs
query = 'name=Alice&age=1'
print(parse_qs(query))
#{'name': ['Alice'], 'age': ['1']}

这是一个反序列化方法,将GET请求转回字典类型。
8.parse_qsl()

1
2
3
4
from urllib.parse import parse_qsl
query = 'name=Alice&age=1'
print(parse_qsl(query))
#[('name', 'Alice'), ('age', '1')]

将参数转化为元组组成的列表
9.quote()

1
2
3
4
5
from urllib.parse import quote
keyword = '壁纸'
url = 'http://www.baidu.com/s?wd='+quote(keyword)
print(url)
#http://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8

这方法是将内容转化为URL编码的格式,将中文转化为URL编码。
10.unquote()
就是URL解码

1
2
3
4
from urllib.parse import unquote
url = 'http://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
#http://www.baidu.com/s?wd=壁纸

分析Robots协议

首先我们来了解一下什么是Robots协议。
该协议也被称为爬虫协议机器人协议,全名叫做网络爬虫排除标准
一般是一个txt文本,放在网站根目录下和王赞的入口文件网在一起,用来告诉爬虫和搜索引擎哪些引擎可以抓取,那些不可以抓取。

1
2
3
User-agent: * 
Disallow: /
Allow: /public/

上面这个robots.txt只允许爬虫爬取public目录。
USer-agent设置为*代表该协议对任何爬取爬虫有效。当设置为User-agent: Baiduspider则代表我们设置的规则只对百度爬虫有效。
禁止所有爬虫爬取任何目录:

1
2
User-agent: * 
Disallow: /

允许所有爬虫爬取任何目录:

1
2
User-agent: * 
Disallow:

只允许某个爬虫访问:

1
2
3
4
User-agent: WebCrawler 
Disallow:
User-agent: *
Disallow: /

这时我们就有一个疑问,爬虫名是如何来的?很多网站都有固定的爬虫名字,可以搜索看看。

了解完robots.txt后, 可以用robotparser模块来解析了。该模块提供了一个类RobotFileParser,可以根据某网站的robots.txt来判断一个爬取爬虫是否有权限来爬取这个网页。

1
2
3
4
5
6
7
8
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('http://www.jianshu.com/robots.txt')
rp.read()
a = rp.can_fetch('*','http://www.jianshu.com/p/b67554025d7d')
b = rp.can_fetch('*','http://www.jianshu.com/search?q=python&page=1&type=collections')
print(a,b)
#True False

set_url()就是设置robots.txt文件链接,如果在创建对象时就已经传入了链接,则不需要该方法。
read()读取robots.txt并进行分析,如果不调用该方法,后续判断都为false,所以一定要调用,但他不会返回任何东西。
can_fetch()第一个参数是User-agent,第二个参数是要抓取的URL判断该搜索引擎是否可以抓取这个URL,返回true or false。
mtime()返回上次抓取分析robots.txt的时间。
modified()将当前时间设置为上次抓取和分析robots.txt的时间。
parse()解析robots.txt,传入的参数是robots.txt的某些行的内容,然后按照robots.txt的语法规则来分析这些内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from urllib.robotparser import RobotFileParser
from urllib.request import urlopen
from urllib.request import Request
rp = RobotFileParser()
# 如果不加上下面的这行出现会出现urllib.error.HTTPError: HTTP Error 403: Forbidden错误
# 主要是由于该网站禁止爬虫导致的,可以在请求加上头信息,伪装成浏览器访问User-Agent
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'}
req = Request('http://www.jianshu.com/robots.txt',headers = headers)

rp.parse(urlopen(req).read().decode('utf-8').split('\n'))
a = rp.can_fetch('*','http://www.jianshu.com/p/b67554025d7d')
b = rp.can_fetch('*','http://www.jianshu.com/search?q=python&page=1&type=collections')
print(a,b)
#True False

requests

基本用法

GET请求

发起GET请求,并添加额外信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
data = {'name':'Alice','age':'1'}
r = requests.get('http://httpbin.org/get',params=data)
print(r.text)
# {
# "args": {
# "age": "1",
# "name": "Alice"
# },
# "headers": {
# "Accept": "*/*",
# "Accept-Encoding": "gzip, deflate",
# "Connection": "close",
# "Host": "httpbin.org",
# "User-Agent": "python-requests/2.18.4"
# },
# "origin": "210.12.101.154",
# "url": "http://httpbin.org/get?name=Alice&age=1"
# }

抓取网页:
这里是结合了正则表达式来匹配所有的问题内容。

1
2
3
4
5
6
7
8
9
import requests
import re
#加上浏览器标志信息,否则知乎禁止抓取
headers = {'User-Agent':'Mozilla/5.0(Macintosh;Inter Mac OS X 10_11_4)AppleWebkit/537.36(KHTML,like Gecko)Chrome/52.0.2743.116 Safari/537.36'}
r = requests.get('http://www.zhihu.com/explore',headers=headers)
pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</a>',re.S)
titles = re.findall(pattern,r.text)
print(titles)
#['\n有哪些知识是你学医之后才知道的?\n', '\nNature 最新研究发现彩色蛋最先由恐龙孵化出来,这揭示了什么?\n', '\n如何看待刘强东告诉明尼苏达性侵案受害者「你可以成为邓文迪那样的女人」?\n', '\n有哪些化学知识概念已经更新或改变,但不为大众所知?\n', '\n抑郁症患者的自杀是毫无征兆的吗?\n', '\n余沧海屠戮林家,方证、冲虚这些德高望重的人为何不替林平之主持公道,还在任我行攻击余沧海的时候舍身保护?\n', '\n影视作品里有哪些不为人知的情节,或者是一些细思极恐,有趣的部分?\n', '\n如何评价布鲁克林 · 贝克汉姆在 instagram 上发布涉嫌种族歧视的内容?\n', '\n得知越来越多的女大学生被包养,已有的价值观开始动摇怎么办?\n', '\n如果是诺夫哥罗德而非莫斯科统一了俄罗斯会怎样?\n']

抓取二进制数据:
图片、音频、视频本质上都是二进制码组成的。如果想要抓取它们,就要得到它们的二进制码。
下面这段就是抓取github的图标并保存

1
2
3
4
import requests
r = requests.get('https://github.com/favicon.ico')
with open('favicon.ico','wb') as f:
f.write(r.content)

添加headers:
通过headers参数来传达头信息,否则不能正常请求网站。

POST请求

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
import requests
data = {'name':'Alice','age':'1'}
r = requests.post('http://httpbin.org/post',data=data)
print(r.text)
# {
# "args": {},
# "data": "",
# "files": {},
# "form": {
# "age": "1",
# "name": "Alice"
# },
# "headers": {
# "Accept": "*/*",
# "Accept-Encoding": "gzip, deflate",
# "Connection": "close",
# "Content-Length": "16",
# "Content-Type": "application/x-www-form-urlencoded",
# "Host": "httpbin.org",
# "User-Agent": "python-requests/2.18.4"
# },
# "json": null,
# "origin": "210.12.101.154",
# "url": "http://httpbin.org/post"
# }

form部分就是我们提交的数据,证明POST请求成功了。

响应

status_code:状态相应码
headers:响应头
cookies:得到Cookies
url:URL
history:请求历史
requests还提供了很多状态内置码
例如:判断结果是不是404,可以用r.status_code == resquests.codes.not_found判断【r = requests.get(‘url’)】

高级用法

文件上传

上传一个favicon.ico文件

1
2
3
4
5
import requests
files = {'files':open('favicon.ico','rb')}
#files = [('files',open('favicon.ico','rb'))]
r = requests.post('http://httpbin.org/post',files=files)
print(r.text)

files可以是字典,也可以是元组。

Cookies

1
2
3
4
5
import requests
r = requests.get('https://www.baidu.com')
print(r.cookies)#RequestCookieJar类型
for key,value in r.cookies.items():
print(key+'='+value)

用Cookies维持登录状态:

1
2
3
4
5
6
import requests
headers = {'Cookie':手动打码,
'Host':'www.zhihu.com',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'}
r = requests.get('http://www.zhihu.com',headers=headers)
print(r.text)

会话维持

在requests中,get和post请求网页实际上是不同的会话,相当于用两个浏览器打开了不同的页面。例如,先用post登陆了某网站,然后用get方法请求个人信息,实际上这相当于打开了两个浏览器,是完全不相关的两个会话,不能获取个人信息。
可以设置相同的cookies,也可以通过下面的方法维持会话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
#设置cookies
requests.get('http://httpbin.org/cookies/set/number/123456')
r = requests.get('http://httpbin.org/cookies')
print(r.text)
# {
# "cookies": {}
# }
#利用Session维持会话
s = requests.Session()
s.get('http://httpbin.org/cookies/set/number/123456')
r = s.get('http://httpbin.org/cookies')
print(r.text)
# {
# "cookies": {
# "number": "123456"
# }
# }

SSL证书验证

发送http请求时,要验证SSL证书,遇到证书不被官方CA机构信任的网站,只要把verify改为false即可
requests.get(‘url’,verify = False)
遇到建议给出证书的警告时,可以用下面两种方法:

1
2
3
4
5
from requests.packages import urllib3
urllib3.disable_warnings()

import logging
logging.captureWarnings(True)

当然,也可以使用本地证书,但要注意key是解密状态的。

代理设置

1
2
3
4
5
6
7
8
import requests
proxies = {
'http':'http//10.10.1.10:3128',#自己设置可用的代理
'https':'http://10.10.1.10:1080',
#也可以用socks协议的代理
'http':'socks5://user:password@host:port'
}
requests.get("http://www.taobao.com",proxies=proxies)

超时设置

设置超时时间,若超过还没得到响应,则报错。该时间为发出请求到服务器返回响应的时间。

1
2
3
import requests
r = requests.get('http://www.taobao.com',timeout=1)
print(r.status_code)

timeout默认为None,意味着永久等待。

身份认证


1
2
import requests
r = requests.get('url',auth=('username','password'))

Prepared Request

将请求表达为数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from requests import Request,Session
url = 'http://httpbin.org/post'
data = {
'name':'Alice',
'age':1
}
headers = {
'User-Agent':'Mozilla/5.0(Macintosh;Inter Mac OS X 10_11_4)AppleWebkit/537.36(KHTML,like Gecko)Chrome/52.0.2743.116 Safari/537.36'
}
s = Session()
#构造Request对象
req = Request('POST',url=url,data=data,headers=headers)
#调用Session的prepared_request()方法将其转化成一个Prepared Request对象
prepped = s.prepare_request(req)
#send()发送
r = s.send(prepped)
print(r.text)

正则表达式

python的re库提供了整个正则表达式的实现。

match()

传入要匹配的字符串和正则表达式,就可以检测是否匹配
注意,它是从字符串的开头开始匹配的,一旦开头匹配不上,则失败

1
2
3
4
5
6
7
8
9
10
11
12
import re
content = 'Hello 123 4567 World_this is a Regex Demo'
print(len(content))
# ^字符串的开头 \s空白字符 \S非空字符 \d数字 \w数字字母下划线 {n}前面的表达式重复n次
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content)
print(result)
print(result.group())#匹配的结果
print(result.span())#匹配的范围
# 41
# <_sre.SRE_Match object; span=(0, 25), match='Hello 123 4567 World_this'>
# Hello 123 4567 World_this
# (0, 25)

匹配目标

从字符串中提取一部分内容,用()将想提取的子字符串括起来,然后用group()方法传入分组的索引剧可以获取提取的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re
content = 'Hello 123 4567 World_this is a Regex Demo'
print(len(content))
result = re.match('^Hello\s(\d+)\s(\d+)',content)
print(result)
print(result.group())
print(result.group(1))
print(result.group(2))
print(result.span())
# <_sre.SRE_Match object; span=(0, 14), match='Hello 123 4567'>
# Hello 123 4567
# 123
# 4567
# (0, 14)

通用匹配

.表示任意字符(除了换行符),*表示无限次
那么开头的例子可以改写成:

1
2
3
4
5
6
7
8
9
10
11
12
import re
content = 'Hello 123 4567 World_this is a Regex Demo'
print(len(content))
#$匹配字符串的结尾
result = re.match('^Hello.*Demo$',content)
print(result)
print(result.group())
print(result.span())
# 41
# <_sre.SRE_Match object; span=(0, 41), match='Hello 123 4567 World_this is a Regex Demo'>
# Hello 123 4567 World_this is a Regex Demo
# (0, 41)

贪婪与非贪婪

1
2
3
4
5
import re
content = 'Hello 1234567 World_this is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$',content)
print(result.group(1))
#7

在贪婪匹配下,.会尽可能多的匹配字符,所以\d+只有一个7
若想提取提出字符串1234567,就使用非贪婪匹配.
?:

1
2
3
4
5
6
import re
content = 'Hello 1234567 World_this is a Regex Demo'
# ?匹配0个或1个前面定义的片段
result = re.match('^He.*?(\d+).*Demo$',content)
print(result.group(1))
#1234567

但如果.*?是在结尾,则可能匹配不到任何内容

修饰符

.?是不能匹配换行符的,如果字符串存在换行符,那么可以在re.match()中加入re.S,比如result = re.re.match(‘^He.?(\d+).*Demo$’,content,re.S)
常用的有:re.I使对大小写不敏感;re.L做本地化识别匹配;re.M多行匹配

转义匹配

遇到要转义的字符使用\

弥补了match必须开头就要匹配的缺点,扫描整个字符串,返回第一个匹配的结果。
re.S可以跳过换行符,十分重要。

findall()

返回取所有匹配的字符串

sub()

修改字符串

1
2
3
4
5
import re
content = 'This1 i2s a d3emo4'
result = re.sub('\d+','',content)
print(result)
#This is a demo

compile()

将正则字符串编译成正则表达式对象,以便后面的匹配中使用。

1
2
3
4
5
6
7
8
9
10
import re
content1 = '2018-05-06 13:00'
content2 = '2018-05-09 14:00'
content3 = '2018-11-05 13:14'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern,'',content1)
result2 = re.sub(pattern,'',content2)
result3 = re.sub(pattern,'',content3)
print(result1,result2,result3)
#2018-05-06 2018-05-09 2018-11-05

实例:抓取猫眼电影排行榜

具体过程看下一篇文章😂