本页源码可以在这里查看或下载。

1.urllib库

1.发送请求

(1)request:可以方便地实现请求的发送并得到响应。

# urlopen test
import urllib.request
responce = urllib.request.urlopen('https://www.python.org')
print(responce.read().decode('utf-8'))

我们可以看见在控制台上输出了网页的源码。

我们在上面代码后加上一句

print(responce.status)
print(responce.getheaders())
print(responce.getheader('Server'))

输出

可以看到responce中的状态码、报头、报头中的某一项。
我们还可以传递一些参数
urllib.request.urlopen(url,data=None,[timeout, ]*,cafile=None,capath=None,cadefault=Flase,context=None)
其中:
data:
可选参数,以POST方式提交表单。
添加代码:

import urllib.parse
data = bytes(urllib.parse.urlencode({'word': 'hello'}),encoding= 'utf-8')
responce = urllib.request.urlopen("http://httpbin.org/post",data=data)
print(responce.read())

运行结果

b'{
"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":"124.93.197.137",
"url":"http://httpbin.org/post"
}\n'

这里我们传递了一个参数word,值是hello,它被转码成bytes(字节流)类型。
这里的请求站点是httpbin.org,提供HTTP请求测试,我们看到表单中已经有 “word”:”hello”。
 
timeout:
用于设置超时时间,单位为秒,到时没得到响应则抛出异常,如果不指定默认全局参数。
添加代码:
我瞎打的网站,这是个根本不存在的网站,所以肯定会超时。

# urlopen-timeout test
responce = urllib.request.urlopen('http://www.google.com', timeout=1)
print(responce.read())

于是我们意料之中的得到了报错。
urllib.error.URLError: <urlopen error timed out>
context:必须是ssl.SSLContext类型,用来指定SSL设置。
cafile和capath:指定CA证书和它的路径。
cadefault:已经弃用,默认为Flase。
 
(2)request
request对象可以更加灵活的配置参数
class urllib.request.Request(rtl, data=None, header={}, origin_req_host=None, unverifiable=Flase, method=None)
其中:
url:请求的URL,必选参数
data:可选参数,一定是bytes类型,如果是字典,需要先用urllib.parase模块里的urlencode()编码。
headers:请求头,可以在构造请求时直接构造,也可以调用add_header()方法添加,通常用来伪装成浏览器。
origin_req_host:请求方的host名称或者ip地址。
unverifiable:表示请求是否是无法验证的,默认是Flase,意思是用户没有足够的权限来选择接收这个请求的结果。
method:请求的方法。POST、GET、PUT等。
添加代码:

# request test with factor
url = 'http://httpbin.org/post'
headers = {
    "User-Agent": 'Mozilla/4.0(compatible;MSIE 5.5;Windows NT)',
    'Host': 'httpbin.org'
}
dict = {
    'name': 'Sniper'
}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
req = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
responce = urllib.request.urlopen(req)
print(responce.read().decode('utf-8'))

发现返回的数据已经根据我们发过去的报头变化了。

{
"args":{},
"data":"",
"files":{},
"form":{
"name":"Sniper"
},
"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;MSIE 5.5;Windows NT)"
},
"json":null,
"origin":"124.93.197.193",
"url":"http://httpbin.org/post"
}

 
我们还可以使用更高级的工具Handler,可以理解为各种处理器,有专门处理登陆请求的,有专门处理Cookies的,有专门处理代理的等。
BaseHandler类,它是所有其他Handler的父类,它提供了最基本的方法,例如default_open()、protocol_request等。
HTTPDefaultErrorHandler:用于处理HTTP响应错误,错误都会抛出HTTPError类型的异常。
HTTPRedirectHandler:用于处理重定向。
HTTPCookieProcessor:用于处理Cookies。
ProxyHandler:用于设置代理,默认代理为空。
HTTPPasswordMgr:用于管理密码,它维护了用户名和密码的表。
HTTPBasicAuthHandler:用于管理认证,如果一个链接打开时需要认证,那么可以用它来解决认证问题。
验证:
添加代码:

# Opener test
from urllib.request import HTTPPasswordMgrWithPriorAuth, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
username = "admin"
password = ''
url = 'http://192.168.0.1/index.asp'
p = HTTPPasswordMgrWithPriorAuth()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)
try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

运行结果,可以看到了显示了通过验证的源码:

代理:

# Proxy test
proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:9743',
    'https': 'https://127.0.0.1:9743'
})
opener = build_opener(proxy_handler)
try:
    # responce = opener.open('http://www.sniper97.cn')
    responce = opener.open('https://python.org')
    print(responce.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

 
Cookies:

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


我们还可以将Cookies输出至文件:

# Cookies test : save in file
filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename) # LWPCookieJar is always ok,they are two style
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = build_opener(handler)
responce = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

MozillaCookieJar():

LWPCookieJar():

我们使用这个文件里的Cookies访问百度:

# Cookies test : use Cookies
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookies.txt',ignore_discard=True,ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = build_opener(handler)
responce = opener.open('http://www.baidu.com')
print(responce.read().decode('utf-8'))

运行结果,百度源码被返回:

 

2.处理异常

urllib的error模块定义了由request模块产生的异常。
(1)URLError
像这样,如果URL不存在,就会抛出Not Found异常。

# URLError test
try:
    responce = urllib.request.urlopen('https://www.baidu.com')
except URLError as e:
    print(e.reason)

(2)HTTPError
是URLError的子类,专门用来处理HTTP请求错误,比如认证请求等。
像这样,如果URL不存在,就会抛出Not Found异常。

# HTTPError test
try:
    responce = urllib.request.urlopen('http://www.baidu.com')
except HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')

 

3.解析链接

(1)urlparse()
该方法可以实现URL的识别和分段。

# urlparse test
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result), result)

程序输出:

<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

其中,最后一个fragment是锚点,用于直接确定界面的下拉位置。
实际上返回的result是一个元组,我们可以用下标或者属性名来获取。
(2)urlunparse()
这相当于urlparse的逆向,传入参数的长度必须是6

# urlunparse test
data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))

运行结果:

(3)urlsplit()
和urlparse()类似,只不过不再单独解析params这一项,只返回5个结果。

# urlsplit test
result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result), result, sep='\n')

运行结果:

(4)urlunsplit()
和urlunparse()类似,同样只有5个参数。

# urlunsplit test
data = ['http', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))

运行结果:

(5)urljoin()
一直生成链接的方法,一个基础链接作为第一个参数,新的链接作为第二个参数,返回以新链接为准,但是如果新链接的成分不完整,则和基础链接合成一个完整链接返回。
(6)urlencode()
自动生成GET请求URL。

# urlencode test
params = {
    'name': 'Sniper',
    'age': '21'
}
base_url = 'http://www.baidu.com?'
url = base_url+urlencode(params)
print(url)

运行结果:

(7)parse_qs( )
urlencode( )的逆过程,返回值为字典。

# parse_qs test
query = '?name=Sniper&age=21'
print(parse_qs(query))

运行结果:

(8)parse_qsl()
urlencode( )的逆过程,返回值为元组组成的列表。
(9)quote()
将内容转为URL编码格式。(用在URL中有中文时)

# quote test
keyword = '一个不会玩狙的天才狙击手'
url = 'https://www.baidu.com/s?wd='+quote(keyword)
print(url)

程序输出:

我们点击这个链接:https://www.baidu.com/s?wd=%E4%B8%80%E4%B8%AA%E4%B8%8D%E4%BC%9A%E7%8E%A9%E7%8B%99%E7%9A%84%E5%A4%A9%E6%89%8D%E7%8B%99%E5%87%BB%E6%89%8B
会发现百度搜索了我们的keyword。
(10)unquote()
quote()的逆过程。
4.Robots协议
利用urllib中的Robotparser模块,我们可以实现网站的Robots协议的分析。
(1)robots协议
是用来规定哪些页面可以抓取,哪些不可以,通常存在一个叫做robots.txt的文件中,存放在服务器端。
robots有以下几个字段:
User-agent:  # 用来指定对哪些爬虫有效,比如Baiduspider(百度)、*(所有)。
Disallow: # 不允许哪些页面被抓取。
Allow: # 允许哪些页面被抓取。
(2)爬虫名称
固定的名字,比如:
BaiduSpider      百度
Googlebot         谷歌
360Spider         360搜索
YodaoBot         有道
is_archiver       Alexa
Scooter            altavista
(3)robotparser()
可以用这个方法来解析robots.txt
set_url():用来设置robots.txt的链接,如果在创建RobotFileParser()的时候传入了URL那么就不需要调用该方法。
read():读取robots.txt文件,这个方法执行一个读取和分析的操作,虽然没有返回结果但是必须调用。
parse():解析robots.txt。
can_fetch():传参有两个,第一个是User-agent,第二个是URL,返回的是该搜索引擎是否可以抓取这个URL,值为True或Flase。
mtime():返回上次抓取和分析robots.txt的时间。
modified():将当前时间设置为上次抓取和分析robots.txt的时间。

# robotparser test
rp = RobotFileParser()
rp.set_url('http://www.jianshu.com/robots.txt')
rp.read()
print(rp.can_fetch('*', 'http://www.jianshu.com/p/b67554025d7d'))
print(rp.can_fetch('*', 'http://www.jianshu.com/search?q=python&page=1&type=collections'))

 

2.requests

1基本用法

# requests test
r = requests.get('http://www.baidu.com')
print(type(r))
print(r.status_code)
print(type(r.text))
print(r.text)
print(r.cookies)

运行结果:

我们可以看到,它的响应类型是requests.models.Response,响应体的类型是str,Cookies的类型是RequestsCookieJar。
 
GET请求:

# requests test -> get
data = {
'name': 'Spider',
'age': '21'
}
r = requests.get('http://httpbin.org/get',data)
print(r.text)

运行结果:

{
"args":{
"age":"21",
"name":"Spider"
},
"headers":{
"Accept":"*/*",
"Accept-Encoding":"gzip, deflate",
"Connection":"close",
"Host":"httpbin.org",
"User-Agent":"python-requests/2.19.1"
},
"origin":"124.93.197.137",
"url":"http://httpbin.org/get?name=Spider&age=21"
}

我们可以看到url部分,是使用的get方法访问的。
我们使用URL+正则爬取知乎。
这里必须加上headers,否则会被知乎拒绝访问。

# requests test -> zhihu
headers = {
    'User-agent': 'Mozilla/5.0(Macintosh;Intel Mac OS X 10_11_4)AppleWebKit/537.36(KHTML, like Gecko)Chrome/52.0.2743.116 Safari/537.36'
}
r = requests.get('https://www.zhihu.com/explore',headers=headers)
pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</a>',re.S)
titles = re.findall(pattern, r.text)
print(titles)

其中正则表达式根据分析页面源码得出

我们运行结果和当前知乎的页面:
 

抓取结果和页面问题一致。

 
抓取二进制文件

# requests test -> binary file
r= requests.get('https://github.com/favicon.ico')
print(r.text)
print(r.content)
with open('favicon.ico', 'wb')as f:
    f.write(r.content)

我们看到两个输出输出的是乱码和十六进制。

这时候我们以二进制写格式打开一个文件,将数据写入,我们发现图片爬取成功。

添加headers:
上栗对知乎的爬取中使用过。
 
POST请求:

# requests test -> post
data = {
    'name': 'Spider',
    'age': '21'
}
r= requests.post('http://httpbin.org/post', data=data)
print(r.text)

运行结果:

 
响应:

# requests test -> responce
headers = {
    'User-agent': 'Mozilla/5.0(Macintosh;Intel 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.jianshu.com',headers=headers)
print(type(r.status_code), r.status_code)
print(type(r.headers), r.headers)
print(type(r.cookies), r.cookies)
print(type(r.url), r.url)
print(type(r.history), r.history)

运行结果:

 
文件上传:
使用POST上传。

# requests test -> post
files = {
    'file':open('favicon.ico', 'rb')
}
r = requests.post('http://httpbin.org/post',files=files)
print(r.text)

运行结果:

 
Cookies:

# requests test -> cookies
r= requests.get('http://www.baidu.com')
print(r.cookies)
for key, value in r.cookies.items():
    print(key+'='+value)

用Cookies维持登录:
我们先把知乎请求的cookies复制下来:

编写代码爬取个人主页:

# requests test -> cookies -> zhihu
headers = {
    'Cookies': '_xsrf=Buel2ZkWUtC7SgTSB8EJPCF4Br5Rcimr;'
               ' _zap=1f70cf3c-9b1a-4513-a8f3-d6abb4270880;'
               ' d_c0="AMCkg9ZJ7w2PTnLDQAncDVfCUc59_P4qggs=|1532176674";'
               ' q_c1=6e44fd4dd5064217a670c1c5aaf31711|1532176674000|1532176674000;'
               ' capsion_ticket="2|1:0|10:1532176678|14:capsion_ticket|44:NWQ1YWNkNzU5ZjhiNGJmYmI0NTkyNmJiYzg4OWM5YzM=|67f43de453cf2ec1315e2bc98b734af6bef647447e40c73341943d6a8a2b4724";'
               ' z_c0="2|1:0|10:1532176688|4:z_c0|92:Mi4xemN3Z0FnQUFBQUFBd0tTRDFrbnZEU1lBQUFCZ0FsVk5NSGRBWEFEVnFndHJQYUZ1OWNXdHJidTJ4eldhX0NkRUFn|8155841af129425202e2ef982f977517d7940a6257866e3df4221696ff4710ef"',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
r= requests.get('https://www.zhihu.com/people/zhao-yu-12-34/activities', headers=headers)
print(r.text)

我们先看主页部分:

爬取的代码中可以看到对应的文字,说明爬取的时候是维持登录状态的。
 
会话维持:
我们可以使用Cookies进行会话维持,但是那太麻烦了,我们还可以使用Session进行会话维持。

# requests test -> session
requests.get('http://httpbin.org/cookies/set/number/123456789')
r= requests.get('http://httpbin.org/cookies')
print(r.text)
s = requests.session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)

输出:

发现第一次如果直接访问两次,cookies为空,第二次获取到session再访问,cookies为第一次访问时设置的cookies。
 
SSL证书验证:
requests还提供了证书验证的功能。我们可以使用verify参数控制是否检查证书,默认值为True,自动验证。
https需要一个CA证书认证,但是12306的认证是铁道部自行签发的,不被CA认证机构所信任,所以如果爬取12306就需要设置不检查。
不加verify参数:

增加verify参数,虽然会提出警告,但是状态码正常返回,并且退出码也是0:
 
代理:
我们需要设置代理来解决因为高频访问导致的拒绝访问或者验证码这类问题,需要用到proxies参数。

# request test -> proxies
proxies = {
    'http': 'http://xxx.xxx.xxx.xxx:xxxx',
    'https': 'https://xxx.xxx.xxx.xxx:xxxx'
}
r = requests.get('https://www.taobao.com', proxies=proxies)
print(r.text)

 
超时设置:
防止时间太久没有响应,使用timeout参数。

# requests test -> timeout
r = requests.get('https://www.taobao.com', timeout=1)
print(r.text)

 
身份验证:

# requests test -> auth
r = requests.get("http://192.168.0.1/index.asp", auth=('admin', ''))
print(r.status_code)
print(r.text)

 
Prepared Request:
用来将请求表示为数据结构。

# requests test -> prepare_request
url = 'http://httpbin.org/post'
data = {
    'name': 'Sniper'
}
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
s = Session()
req = Request('POST', url, data=data, headers=headers)
prepped = s.prepare_request(req)
r = s.send(prepped)
print(r.text)

结果:

这里我们引入Request,然后url、data和headers参数构造了一个Request对象,这时再调用Session的prepare_request()方法将其转换成一个Prepared Request对象,然后调用send()方法发送即可。
 

3.正则表达式

\w      匹配字母、数字及下划线
\W      匹配不是字母、数字及下划线的字符
\s      匹配任意空白字符,等价于[\t\n\r\f]
\S      匹配任意非空字符
\d      匹配任意数字,等价于[0-9
\D      匹配任意非数字的字符
\A      匹配字符串开头
\Z      匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串
\z      匹配字符串结尾,如果存在换行,同时还会匹配换行符
\G      匹配最后匹配完成的位置
\n      匹配一个换行符
\t      匹配一个制表符
^      匹配一行字符串的开头
$      匹配一行字符串的结尾
.   匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符
[…]      用来表示一组字符,单独列出。
[^…]      不在[]中的字符。
*      匹配0个或多个表达式
+      匹配1个或多个表达式
?      匹配0个或1个前面的正则表达式定义的片段,非贪婪方式
{n}      精确匹配n个前面的表达式
{n, m}      匹配n到m次由前面正则表达式定义的片段,贪婪方式
a|b      匹配a或b
( )      匹配括号内的表达式,也表示一个组
 
match():
用来检查正则表达式是否匹配字符串。
.group():打出正则表达式匹配的内容。
.span():输出匹配的字符串在原字符串中的相对位置。

# re test -> match
str = 'Hello 123456 123456 World Test'
result = re.match('^Hello.*Test$', str)
print(result)
print(result.group())
print(result.span())

输出:

贪婪&非贪婪:
.* 默认是贪婪的,所以除非是字符串最后,一般加上? 用来定义非贪婪,使目标可以匹配到更多字符。

# re test -> greedy & non-greedy
str = 'Hello 123456 123456 World Test'
result = re.match('^He.*(\d+).*st$', str)
print(result)
print(result.group(0))
print(result.group(1))
print(result.span())
result = re.match('^He.*?(\d+).*?st$', str)
print(result)
print(result.group(0))
print(result.group(1))
print(result.span())

结果:

 
转义字符:
如果原文符号出现在正则表达式中,我们需要进行转义,在符号前面加上\即可 。
 
search():
用来检查正则表达式是否匹配字符串。相比match(),它可以搜索出字符串中的匹配子字符串,而match()则要求字符串开头必须符合正则表达式。
search(‘regex’, ‘str’, re.S)
其中这个re.S是匹配回车,也就是说加了这个正则表达中有回车将被忽略,建议加上,因为很多网页源码中都是有回车的。
findall():
可以返回所有匹配,而不是第一个。
sub():
使用正则表达式修改文本。

# re test -> sub
str = 'weqr65s4agf6354agfa'
result = re.sub('\d+', '', str)
print(result)

程序输出:
weqrsagfagfa
将所有数字都替换成了空。
compile():
该方法可以将正则表达式编译成对象,以便后面多次使用该正则表达式。
 
 
.3


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注