urllib时python的一个内置库,一共包含四个模块
1. request-请求
这是最基本的HTTP请求模块,可以模拟请求的发送。就像在浏览器输入URL,按下回车一样,只需要给库方法传入URL以及额外的参数,就可以模拟实现发送请求的过程了
urlopen
发送请求。
urllib.request模块提供了最基本的构造HTTP请求的方法,利用这个模块可以模拟浏览器请求的发起过程,同时它还具有处理授权验证,重定向, 浏览器Cookie以及其他的一些功能
这里以百度为例
说明: 这里只是单纯的使用一下 `urlopen` 所以获取的网页内容并不重要
import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')
print(response.read().decode('utf-8'))
查看返回的响应
import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')
print(type(response))
输出:
<class 'http.client.HTTPResponse'>
响应式一个 HTTPResponse 对象,主要包含: read, readinto, getheader, getheaders, fileno,
等方法, 以及 msg, version, status, reason, debuglevel, closed等属性
可以通过上述方法和属性来对网页进行具体操作
import urllib.request
response = urllib.request.urlopen('https://baidu.com')
print(response.status) # 查看状态码
print(response.getheaders()) # 查看相应头信息
print(response.getheader('Server')) # 获取响应头中的 Server 的值
输出:
200 [('Bdpagetype', '1'), ('Bdqid', '0x89f2326c001257e1'), ('Content-Length', 省略一大堆 BWS/1.1
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None )
参数说明:
url: 为必传, 网页地址
data: 可选参数。在添加该参数时,需要使用bytes方法将参数转化为字节流编码格式的内容,即bytes类型。另外如果传递了该内容,那么请求方式就不再时 GET 而是 POST 了
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode())
输出:
{ "args": {}, "data": "", "files": {}, 下面是我们传递的参数: "form": { "name": "germey" }, "headers": { "Accept-Encoding": "identity", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "www.httpbin.org", "User-Agent": "Python-urllib/3.11", "X-Amzn-Trace-Id": "Root=1-66934f46-661cf2f9337c4b704676c9d8" }, "json": null, "origin": "117.152.24.63", "url": "https://www.httpbin.org/post" }
timeout: 可选参数,设置超时时间,单位为秒,这个参数设置的是请求和响应的总时间。如果需要分别设置可以以元组的形式 例如 (3,2),如果不设置,则使用全局默认时间,这个参数支持HTTP , HTTPS, FTP请求
import urllib.request
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
print(response.read())
超时报错,输出:
URLError: <urlopen error timed out>
也可以使用try except 来捕获异常实现当网页长时间未响应时,跳过或继续等待
import urllib.request
import socket
import urllib.error
try:
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
输出:
TIME OUT
context: 可选参数,该参数必须是ssl.SSLContext类型, 用来指定SSL的设置
cafile和capath:可选参数, 分别来指定CA证书和其路径
cadefault: 可选参数, 貌似已经弃用了,默认为False
Request
urlopen 只能发送简单的请求,如果要加如 Headers 就需要使用Request来构建请求了
简单使用:
import urllib.request
request = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(request)
print(response.read().decode())
构造方法:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
url: 除了 url 是必传参数外, 其余都是可选参数
data: 要传的数据, 必须是bytes类型,如果是字典,可以先用urllib.parse模块中的urlencode方法进行编码
headers: 是一个字典,这就是请求头,在我们构造请求时,可以通过headers参数直接构造此项,也可以通过调用请求实例的add_header方法添加
添加请求头常见的方法是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib
origin_req_host: 是指请求方的host名称 或者 IP地址
unverifiable: 表示请求是否是无法验证的,默认值是False, 意思是用户没有足够的权限来接收这个请求的结果
method: 是一个字符串, 用来指定请求时使用的方法 例如 GET POST PUT 等
简单使用
from urllib import request, parse
url = 'https://www.httpbin.org/post'
headers = {'User-Agen': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
'Host': 'www.httpbin.org'}
dict = {'name': 'germey'}
data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
通过add_header 方法添加 headers
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36')
Handler
Handler可以理解为各种处理器,有专门处理登录验证,处理Cookie, 处理代理设置利用这些Handler几乎可以实现HTTP请求中的所有功能
urllib.request模块中的BaseHandler类是所有Handler类的父类,它提供了最基本的方法
会有各种子类继承BaseHandler类
-
HTTPDefaultErrorHandler 用来处理HTTP中的响应错误,所有错误都会抛出HTTPError类型异常
-
HTTPRedirectHandler 用来处理重定向
-
HTTPCookieProcessor 用于处理Cookie
-
ProxyHandler 用于处理代理,代理默认为空
-
HTTPPasswordMgr 用于管理密码,它维护着用户名密码的对照表
-
HTTPBasicAuthHandler 用于管理认证,如果一个链接在打开时需要认证,那么可以用这个类来解决认证问题
验证
在访问网站时,可能会弹出需要登录的窗口,遇到这种情况,就表示这个网站启用了基本身份认证,这是一种登录验证的方式,爬虫可以借助HTTPBasicAuthHandler模块来模拟登录
简单示例: 示例网站: https://ssr3.scrape.center
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center/'
p = HTTPPasswordMgrWithDefaultRealm()
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)
代码说明:
这里首先实例化了一个 HTTPBasicAuthHandler 对象 auth_handler 其参数是 HTTPPasswordMgrWithDefaultRealm 对象, 它利用 add_password方法添加了 用户名和密码
这样就建立了一个用来处理验证的Handler类
然后将刚建立的auth_handler 类 当作参数传入 bulid_opener方法, 构建一个Opener,这个Opener 发送请求时,就相当于验证成功了
最后利用Opener 类中中的open 方法打开链接,即可完成验证,这里获取的结果就是验证成功后的页面源码内容
代理
做爬虫的时候免不了要使用代理,如果要添加代理可以这样做
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = ProxyHandler({
'http': 'http://127.0.0.1:8080',
'https': 'https://127.0.0.1:8080'
})
openr = build_opener('https://www.baidu.com')
try:
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)
首先上面的代码时不能运行的,因为代理使用的时本地地址, 需要更改代理IP
代码说明:
这里现在本地搭建了一个代理,并让其运行在8080端口上
上面使用了ProxyHandler, 其参数是一个字典,键名是协议类型(HTTP 或 HTTPS等)键是代理的链接,可以添加多个代理
然后利用这个Handler 和 build_opener 方法构建了一个Opener 之后发送请求即可
Cookie
要想处理Cookie 首先要获取Cookie
示例代码:
获取Cookie
import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:
print(item.name + '=' + item.value)
首先必须声明一个Cookie对象,然后利用HTTPCookieProcessor构建一个Handler, 最后利用build_opener 方法构建一个Opener , 执行open即可
保存Cookie
保存Cookie
import urllib.request, http.cookiejar
filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
这里需要将CookieJar 换成 MozillaCookieJar, 它会在生成文件时用到, 是CookieJar的子类,可以用来处理跟Cookie和文件相关的事件, 例如读取和保存Cookie, 可以将Cookie保存成Mozilla型浏览器的Cookie格式
另外, LWPCookieJar 同样也可以读取和保存Cookie, 只是Cookie文件的保存格式和MozillaCookieJar不一样,它会保存成 LWP(libwww-perl)格式
cookie = http.cookiejar.LWPCookieJar(filename)
读取Cookie 文件 这里以 LWPCookieJar格式为例
读取
import urllib.request, http.cookiejar
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode())
这里只是演示读取cookie文件, 所以不要纠结从百度读取了什么,因为这里没有设置headers所以不能读取到真正的内容
这里调用了load 方法来读取本地的Cookie文件, 获取了Cookie的内容,这样做的前提是我们实现生成LWPCookieJar格式的cookie, 并保存了文件,读取cookie后,使用同样的方法构建handler类和opener类 即可完成操作
2. error-异常
异常处理模块。如果出现请求异常,那么我们可以捕获这些异常,然后进行重试或者其他操作来保证程序不会意外终止
URLError
URLErroe类来自urllib库中error模块, 继承自OSError类, 是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理
它具有一个属性 reason 即返回错误的原因
示例:
from urllib import request, error
try:
response = request.urlopen('https://www.baidu.com')
response = request.urlopen('htttps://cuiqingcai.com/404')
print('111')
except error.URLError as e:
print('not Found')
print(e.reason)
输出:
unknown url type: htttps
这里第一运行的时候报错了, 切换网址运行之后,再切换回来又好了,不明所以
HTTPError
HTTPError 是 URLError 的子类, 专门用来处理HTTP请求的错误, 例如认证请求失败等,
有如下三个属性:
code: 返回HTTP状态码
reason: 同父类一样,返回错误的原因
headers: 返回请求头
示例:
from urllib import request, error
try:
response = request.urlopen('https://www.baidu.com')
response = request.urlopen('htttps://cuiqingcai.com/404')
except error.HTTPError as e:
print('not Found')
print(e.reson, e.code, e.headers, sep='\n')
except error.URLError as e:
print(e.reason)
else:
print('Request Successfully')
这里并不能正确的捕获到HTTPError的异常, 暂时不知道原因
有的时候 reason 返回的不一定是字符串也有可能是一个对象
这个时候可以用 isinstance 方法判断类型, 做出更详细的异常判断
from urllib import request, error
import socket
try:
response = request.urlopen('https://www.baidu.com', timeout=0.01)
response = request.urlopen('htttps://cuiqingcai.com/404')
except error.HTTPError as e:
print('not Found')
print(e.reson, e.code, e.headers, sep='\n')
except error.URLError as e:
print(e.reason)
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
else:
print('Request Successfully')
3. parse-解析链接
一个工具模块。它提供了很多URL的处理方法,例如拆分,解析,合并等
它支持如下协议的URL处理:
file, ftp, gopher, hdl, http, https, imap, mailto, mms, news, nntp, prospero, rsync, rtsp, rtspu, sftp, sip, sips, snews, svn, svn+ssh, telnet, wais
urlparse
该方法可以实现URL的识别和分段
示例:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
输出:
<class 'urllib.parse.ParseResult'> ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以看到解析的对象是一个 ParseResult类型的对象,包含六部分,分别是: scheme, netloc, path, params, query 和 fragment
从解析的地址可以看出urlparse 方法再解析URL时有特定的分割符,:// 前面的内容是scheme, 代表协议, 第一个 / 前面是netloc, 即域名; 后面是path, 即访问路径, 分号 ; 后面是params, 代表参数, 问号 ? 后面是查询条件 query , 一般用作GET类型的URL, 井号 # 后面是锚点 fragment,用于直接定位页面内部的下拉位置
于是可以得出一个标准的链接格式:
scheme://netloc/path;params?query#fragment
urlparse API:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
urlstring: 必填, 待解析的URL
scheme: 这是默认的协议(如 http 或 https 等) 如果待解析的URL没有带协议信息,则取默认
示例:URL不带协议
result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
输出:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
示例:URL带协议
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', scheme='http')
print(result)
输出:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
从上面两个例子可以看出, scheme 参数只在 URL不带协议的时候生效
allow_fragments: 是否忽略fragment。 如果此项被设置为False, 那么fragment部分就会被忽略,它就会被解析为 path, params 或者 query 的一部分, 而 fragment部分为空
示例:设置allow_fragments为False
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
输出:fragment 解析到了 query中
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
示例:URL中不带parmas和 query
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
输出:fragment 解析到了 path 中
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
返回结果 ParseResult 实际上是一个元组, 既可以用属性名获取其内容,也可用索引获取
示例:
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')
输出:
https https www.baidu.com www.baidu.com
urlunparse
有了urlparse 就有 urlunparse , 用来构造URL。 这个方法接收的参数是要给可迭代对象,其长度必须是6, 否则会抛出参数量不足或过多的异常
示例:
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
输出:
这里的参数data 用了列表类型, 也可以用其他类型, 例如元组或特定的数据结构
urlsplit
这个方法和urlparse 很相似, 只不过它不在解析 parmas 这一部分(parmas 会合并到 path中), 只返回 5 个结果
示例:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result)
输出:
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
返回的结果是一个 SplitResult, 也是一个元组 ,可以通过索引和属性名获取内容
示例:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result.scheme, result[0])
输出:
https https
urlunsplit
与 urlunparse 方法类似, 这也是将链接的各个部分组合成完整链接的方法, 传入的参数是一个可迭代对象, 如列表, 元组等。 唯一区别是,这里参数长度必须是 5
示例:
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
输出:
https://www.baidu.com/index.html?a=6#comment
urljoin
urlunparse 和 urlunsplit 方法都能完成链接的合并, 但都有特定的长度,链接的每一部分都要清晰的分开
而 urljoin 方法 我们可以提供一个 base_url 链接(基础链接) 作为第一个参数, 将新的链接作为第二个参数, urljoin 会分析 base_url 的 scheme, netloc, path 这三个内容, 并对新链接缺失的部分进行补充, 最后返回结果
示例:
from urllib.parse import urljoin
print(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php'))
print(urljoin('https://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2'))
输出:
https://www.baidu.com/FAQ.html https://cuiqingcai.com/FAQ.html https://cuiqingcai.com/FAQ.html https://cuiqingcai.com/FAQ.html?question=2 https://cuiqingcai.com/index.php https://www.baidu.com?category=2#comment www.baidu.com?category=2#comment www.baidu.com?category=2
可以发现, base_url 提供了三项内容: scheme, netloc 和 path 如果新链接里不存在这三项,
就予以补充, 如果存在,就用新链接里面的,base_url 中的不起作用
urlenclde
它再构造GET请求的时候很有用
示例:
from urllib.parse import urlencode
params = {
'name' : 'germey',
'age': 25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
输出:
https://www.baidu.com?name=germey&age=25
parse_qs
有了序列化,就有反序列化。 利用parse_qs 方法, 可以将一个GET请求参数转换成字典
示例:
from urllib.parse import parse_qs
query = 'name=germey&age=25'
print(parse_qs(query))
输出:
{'name': ['germey'], 'age': ['25']}
parse_qsl
parse_qsl 方法将参数转化为 由 元组组成的列表
示例:
from urllib.parse import parse_qsl
query = 'name=germey&age=25'
print(parse_qsl(query))
输出:
[('name', 'germey'), ('age', '25')]
quote
该方法可以将内容转化为 URL 编码的格式。 当URL中带有中文参数时, 有可能会导致乱码问题,此时用 quote 方法可以将中文字符转化为URL 编码
示例:
from urllib.parse import quote
keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
输出:
https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
4. robotparser-分析Robots协议
主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以爬,用的很少
Robots协议: 也称为 爬虫协议 ,机器人协议,全名为 网络爬虫排除标准,用来告诉引擎哪些页面可以抓取,哪些不可以。
它通常是一个叫做robots.txt的文本文件, 一般放在网站的根目录下
搜素爬虫在访问一个站点的时候, 首先会检查这个站点的根目录下是否存在robots.txt 文件, 如果存在,就会根据其中定义的爬取范围来爬取,如果没有,则会爬取所有可直接访问的页面
robots.txt文件示例:
User-agent: *
Disallow: /
Allow: /public/
这里限定了所有搜素爬虫只能爬取public目录。
User-agent: 描述了搜索爬虫的名称, 设置为* 代表所有爬虫,也可以指定名称
例如: User-agent: Baiduspider
这代表设置规则对百度爬虫有效,可以设置多个,但最少要有一条
Disallow: 指定了不允许爬取的目录, 设置为 / 代表不允许爬取所有页面
Allow: 一般不会单独使用, 会和 Disallow 一起用, 用来排除某些限制。上面设置为 /public/ , 结合 Disallow 的设置, 表示所有页面都不允许爬取, 但可以爬取 public 目录
robots.txt 也可以是空文件
一些爬虫名称
|-------------|-------|
| BaiduSpider | 百度 |
| Googlebot | 谷歌 |
| 360Spider | 360搜素 |
robotparser
了解了 Robots协议之后, 就可以使用 robotparser 模块来解析 robots.txt文件了。该模块为我们提供了一个类: RobotFileParser, 它可以根据一个网站的robots.txt文件来判断一个爬取爬虫是否由权限爬取这个网页
这个方法只需要在构造方法中传入robots.txt 文件的链接即可
urllib.robotparser.RobotFileParser(url='')
也可以不在声明中传入链接, 就让其默认为空, 最后再使用 set_url() 方法设置也可以
下面列出 RobotFileParser 的几个常用方法
set_url: 用来设置 robots.txt 文件的链接。 如果再创建的时候传入了,就不需要了
read: 读取 robots.txt 文件进行分析。 注意:这个方法执行读取和分析操作, 如果不调用这个方法,那么接下来的操作都会是 False,这个方法不返回任何内容, 但执行了读取操作
parse: 用来解析 robots.txt 文件, 传入其中的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容
can_fetch: 该方法由两个参数,第一个是User-Agent, 第二个是要抓取的URL。返回的结果是True或False 表示搜素引擎是否可以抓取这个URL
mtime: 返回上次抓取和分析 robots.txt 文件的时间
modified: 设置当前时间为上次抓取的时间
示例:
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://www.baidu.com/robots.txt')
rp.read()
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
传递URL的方法也可以为:
rp = RobotFileParser('https://www.baidu.com/robots.txt')
输出:
True True False
由此可以看出,百度限制了 谷歌对 homepage的抓取,而自己却是可以抓取的
这里同样可以使用 parse 方法执行对 robots.txt 的读取和分析
示例:
from urllib.robotparser import RobotFileParser
from urllib.request import urlopen
rp = RobotFileParser()
rp.parse(urlopen('https://www.baidu.com/robots.txt').read().decode('utf-8').split('\\n'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
输出:
True True False
可以看出,结果是一样的