爬虫基础_urllib

urllib库的结构

urllib库包含以下四个模块:

  • request: 基本的HTTP请求模块
  • error: 异常处理模块
  • parse: 工具模块
  • robotparser: 识别robots.txt的模块

urlopen方法

使用urlopen方法可以发送简单请求

API

urllib.request.urlopen(url, data=None, [timeout,] *, cafile=None, capath=None, cadefault=False, context=None)

  • url: 要请求的URL
  • data: 请求携带的参数, 如果设置了此参数, 请求方式将变为POST, 而不是GET
  • timeout: 超时时间, 单位为秒, 超时抛出URLError异常
  • cafile: CA证书
  • cspath: CA证书的路径
  • cadefault: 已弃用, 默认False
  • context: 用来指定SSL设置, 值必须是ssl.SSLContext类的对象

另外, urlopen方法还可以接受一个Request对象作为参数, 详见后文

发送GET请求

from urllib.request import urlopen

url = 'https://www.python.org'
resp = urlopen(url=url)
print(resp.read().decode('utf-8'))  # read()方法返回的数据是字节, 需要手动解码

发送POST请求

from urllib.request import urlopen
from urllib.parse import urlencode

url = 'https://www.httpbin.org/post'
data = {'name': 'germey'}
# 使用urlencode将数据编码, 再由bytes转为字节
data = bytes(urlencode(data), encoding='utf-8')
# 携带data之后, 请求方式变为POST
resp = urlopen(url=url, data=data)
print(resp.read().decode('utf-8'))

处理超时

import socket
from urllib.request import urlopen
from urllib.error import URLError

url = 'https://www.httpbin.org/get'
try:
    resp = urlopen(url=url, timeout=0.1)  # timeout单位为秒
    html = resp.read().decode('utf-8')
    print(html)
except URLError as e:  # 超时抛出URLError异常
    if isinstance(e.reason, socket.timeout):  # 判断异常具体类型
        print('TIME OUT')

Request类

Request类能够添加更多的请求信息, 例如请求头信息, 请求方式等

API

class urllib.request.Request(url, data=None, headers={}, origin_rep_host=None, unverifiable=False, method=None)

  • url: 要请求的URL
  • data: 要传递的数据, 必须是bytes类型
  • headers: 请求头信息, 类型是字典, 请求头信息可以通过headers参数传递, 也可以通过Request对象的add_header方法传递
  • origin_req_host: 请求方名称或IP地址
  • unverifiable: 请求是否是无法验证的
  • method: 请求方式

使用方法

from urllib.request import Request
from urllib.request import urlopen
from urllib.parse import urlencode

url = 'https://www.httpbin.org/post'
data = bytes(urlencode({'name': 'germey'}), encoding='utf-8')
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36',
    'host': 'www.httpbin.org',
}
req = Request(url=url, data=data, headers=headers, method='POST')
resp = urlopen(req)  # 仍然使用urlopen发送请求, 传入Request对象作为参数
print(resp.read().decode('utf-8'))

使用Handler

使用Handler可以处理一些请求过程中的特殊情况, 比如登录验证, Cookie, 代理等

基类urllib.request.BaseHandler提供最基本的方法, 比如default_open, protocol_request等

各种Handler子类继承了BaseHandler, 处理各种情况:

  • HTTPDefaultErrorHandler: 处理HTTP相应错误, 抛出HTTPError异常
  • HTTPRedirectHandler: 处理重定向
  • HTTPCookieProcessor: 处理Cookie
  • ProxyHandler: 设置代理, 默认为空
  • HTTPPasswordMgr: 管理密码, 维护着用户名密码的对照表
  • HTTPBasicAuthHandler: 处理认证问题

处理登录验证

from urllib.request import HTTPPasswordMgrWithDefaultRealm
from urllib.request import HTTPBasicAuthHandler
from urllib.request import build_opener
from urllib.error import URLError

url = 'https://ssr3.scrape.center/'
username = 'admin'
password = 'admin'

pwd_mgr = HTTPPasswordMgrWithDefaultRealm()  # 创建密码管理器实例
pwd_mgr.add_password(None, url, username, password)  # 添加用户名密码

auth_handler = HTTPBasicAuthHandler(pwd_mgr)  # 使用密码管理器对象, 创建认证处理器对象
opener = build_opener(auth_handler)  # 使用认证处理器对象, 构建opener, 类似于urlopen, 用于发送请求

try:  # 使用opener.open方法发送请求, 就会携带上面配置的账户信息
    resp = opener.open(url)
    html = resp.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

处理代理

from urllib.error import URLError
from urllib.request import ProxyHandler
from urllib.request import build_opener

url = 'https://www.baidu.com'
proxy_handler = ProxyHandler({  # 创建代理处理器
    'http': 'http://118.190.244.234:3128',
    'https': 'https://118.190.244.234:3128'
})
opener = build_opener(proxy_handler)  # 创建opener
try:  # 发送请求
    resp = opener.open(url)
    html = resp.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

处理Cookie

# 直接输出Cookie
import http.cookiejar
import urllib.request

url = 'https://www.baidu.com'

cookies = http.cookiejar.CookieJar()  # 创建CookieJar对象
handler = urllib.request.HTTPCookieProcessor(cookies)  # 使用CookieJar对象创建handler对象
opener = urllib.request.build_opener(handler)  # 创建opener
resp = opener.open(url)
for cookie in cookies:  # 通过CookieJar对象即可拿到Cookie信息, 这个对象类似于列表
    # 获取Cookie对象的name和value属性
    print(cookie.name, '=', cookie.value)
# 将Mozilla格式Cookie信息写入文件
import http.cookiejar
import urllib.request

url = 'https://www.baidu.com'

filename = 'bd_m_cookie.txt'  # 要保存Cookie信息的文件名
# MozillaCookieJar可以处理Cookie信息的文件读写, 支持Mozilla格式的Cookie文件
cookies = http.cookiejar.MozillaCookieJar(filename=filename)
handler = urllib.request.HTTPCookieProcessor(cookiejar=cookies)
opener = urllib.request.build_opener(handler)
resp = opener.open(url)
# 将Cookie信息存入文件
cookies.save(ignore_discard=True, ignore_expires=True)

"""文件内容
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file!  Do not edit.

.baidu.com	TRUE	/	FALSE	1675640364	BAIDUID	48B3F4D3CCDDB7205C471C7941363BCE:FG=1
.baidu.com	TRUE	/	FALSE	3791588011	BIDUPSID	48B3F4D3CCDDB72072B89C5EEAF3C1AE
.baidu.com	TRUE	/	FALSE	3791588011	PSTM	1644104364
www.baidu.com	FALSE	/	FALSE	1644104664	BD_NOT_HTTPS	1
"""
# 将LWP格式的Cookie信息写入文件
import http.cookiejar
import urllib.request

url = 'https://www.baidu.com'
filename = 'bd_lwp_cookie.txt'
# 这里需要换成LWPCookieJar, 其它都一样
cookies = http.cookiejar.LWPCookieJar(filename=filename)
handler = urllib.request.HTTPCookieProcessor(cookiejar=cookies)
opener = urllib.request.build_opener(handler)
resp = opener.open(url)
cookies.save(ignore_expires=True, ignore_discard=True)

"""文件内容
#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="519E24A62494ECF40B4A6244CFFA07C3:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2023-02-06 00:13:16Z"; comment=bd; version=0
Set-Cookie3: BIDUPSID=519E24A62494ECF45DB636DC550D8CA7; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2090-02-24 03:27:23Z"; version=0
Set-Cookie3: PSTM=1644106396; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2090-02-24 03:27:23Z"; version=0
Set-Cookie3: BD_NOT_HTTPS=1; path="/"; domain="www.baidu.com"; path_spec; expires="2022-02-06 00:18:16Z"; version=0
"""
# 从文件中读取Cookie信息
import urllib.request
import http.cookiejar

url = 'https://www.baidu.com'
filename = 'bd_lwp_cookie.txt'
cookies = http.cookiejar.LWPCookieJar()
# 加载之前保存的文件
cookies.load(filename=filename, ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookiejar=cookies)
opener = urllib.request.build_opener(handler)
resp = opener.open(url)
html = resp.read().decode('utf-8')
print(html)

处理异常

URLError类

URLError类继承自OSError类, 是urllib.error模块中的基类

在使用urllib发送请求的过程中出现的异常, 都可以用URLError来捕获

URLError有一个reason属性, 表示出错原因

reason可能返回一个字符串, 也可能返回一个Error对象(比如超时的时候返回<class ‘socket.timeout’> 对象)

HTTPError类

HTTPError类是URLError类的子类, 专门处理HTTP请求错误

包含三个属性:

  • code: 响应状态码
  • reason: 出错原因, 可能是字符串, 也可能是对象
  • headers: 请求头信息

代码

import urllib.request
from urllib.error import URLError, HTTPError

url = 'https://cuiqingcai.com/404'

try:
    resp = urllib.request.urlopen(url, timeout=1)
    html = resp.read().decode('utf-8')
    print(html)
except HTTPError as e:
    print(e.reason, e.headers, e.url, e.fp, e.code, sep='\n')
except URLError as e:
    print(type(e.reason), '\n', e.reason)
else:
    print('success')

"""
Not Found
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
ETag: "60789243-247b"
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; connect-src 'self'
x-proxy-cache: MISS
X-GitHub-Request-Id: E15A:6107:132CB29:158E796:61FF1AA9
Accept-Ranges: bytes
Date: Sun, 06 Feb 2022 00:55:58 GMT
Via: 1.1 varnish
Age: 501
X-Served-By: cache-hkg17931-HKG
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1644108959.779112,VS0,VE1
Vary: Accept-Encoding
X-Fastly-Request-ID: cce2ac7f081b0d937fe93e90656fce56b5e6cc03
X-Cache-Lookup: Cache Miss
X-Cache-Lookup: Cache Miss
X-Cache-Lookup: Cache Miss
Content-Length: 9339
X-NWS-LOG-UUID: 17106243714350687226
Connection: close
X-Cache-Lookup: Cache Miss


https://cuiqingcai.com/404
<http.client.HTTPResponse object at 0x0000019F340B1B80>
404
"""

parse模块中的常用方法

urllib.parse模块中提供了很多方法来处理URL

urlparse

urllib.parse.urlparse(url=url, scheme=’’, allow_fragments=True)

  • url: 待解析的URL
  • scheme: 默认的协议, 如果URL不带协议信息, 就用这个
  • allow_fragments: 是否将fragment部分单独拆分出来, 如果设置成False, fragment会跟在其它部分后面
# 利用urlparse将一个URL拆开
from urllib.parse import urlparse

url = 'https://www.baidu.com/index.html;user?id=5#comment'
result = urlparse(url=url, scheme='https', allow_fragments=True)
print(result)  # 返回值类似于一个命名元组, 可以使用索引取值, 也可以使用属性取值
# ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
print(result.scheme)  # 使用属性取值
# https
print(result[1])  # 使用索引取值
# www.baidu.com

urlunparse

与urlparse相反, urlunparse是将URL的各个部分组装起来得到一个完整的URL

urllib.parse.urlunparse(components)

  • components: URL的组件, 接收一个长度固定是6的可迭代对象
# 利用urlunparse拼接URL
from urllib.parse import urlunparse

data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
# https://www.baidu.com/index.html;user?a=6#comment

urlsplit

urlsplit与urlparse类似, 只是将params并入path中

urllib.parse.urlsplit(url, scheme=’’, allow_fragments=True)

# 利用urlsplit拆分URL
from urllib.parse import urlsplit

url = 'https://www.baidu.com/index.html;user?id=5#comment'
print(urlsplit(url))
# SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')

urlunsplit

urlunsplit与urlunparse类似, 只是传入的组件可迭代对象的长度必须为5

# 利用urlunsplit拼接URL
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

urljoin接收一个base_url和另一个url(一般是相对URL), 可以自动分析base_url的scheme, netloc, path部分, 与相对url拼接在一起, 得到一个完整的URL

urllib.parse.urljoin(base, url, allow_fragments=True)

  • base: 基础URL
  • url: 要和基础URL拼接的相对URL
  • allow_fragments: 是否单独拼接fragment部分
from urllib.parse import urljoin

print(urljoin('https://www.baidu.com', 'FAQ.html'))
# https://www.baidu.com/FAQ.html

urlencode

urlencode能将字典形式的参数序列化为字符串形式(“name=germey&age=25”)

from urllib.parse import urlencode

params = {
    'user': 'germey',
    'age':25
}
base_url = 'https://www.baidu.com?'  # 注意自己处理?的问题
url = base_url + urlencode(params)
print(url)
# https://www.baidu.com?user=germey&age=25

parse_qs

parse_qs反序列化GET请求参数字符串, 返回字典形式的参数

from urllib.parse import parse_qs

query = 'name=mergey&age=25'
print(parse_qs(query))
# {'name': ['mergey'], 'age': ['25']}

parse_qsl

parse_qsl的功能与parse_qs类似, 只是返回元组的列表

from urllib.parse import parse_qsl

query = 'name=mergey&age=25'
print(parse_qsl(query))
# [('name', 'mergey'), ('age', '25')]

quote

quote可以将中文字符串转换为带%的16进制数的字符串形式, 以便作为URL中的参数使用

from urllib.parse import quote

keyword = '高清壁纸'
base_url = 'https://www.baidu.com/s?wd='

url = base_url + quote(keyword)
print(url)
# https://www.baidu.com/s?wd=%E9%AB%98%E6%B8%85%E5%A3%81%E7%BA%B8

# 实际上底层的做法就是将中文转换为bytes, 再把每个字节转换为16进制, 再在前面加上%
bs = bytes(keyword, encoding='utf-8')
b_list = []
for b in bs:
    b_list.append(hex(b)[-2:].upper())
b_str = '%' + '%'.join(b_list)
print(b_str)
# %E9%AB%98%E6%B8%85%E5%A3%81%E7%BA%B8

unquote

unquote的功能与quote相反

from urllib.parse import unquote

url = 'https://www.baidu.com/s?wd=%E9%AB%98%E6%B8%85%E5%A3%81%E7%BA%B8'
print(unquote(url))
# https://www.baidu.com/s?wd=高清壁纸

Robots协议

Robots协议也称作爬虫协议, 机器人协议, 全称爬虫排除协议(Robots Exclusion, Protocol), 用来告诉爬虫, 哪些页面可以抓取, 哪些不可以

它通常是一个名为robots.txt的文件, 放在网站的根目录

robots.txt文件中一般有三种类型的条目:

  • Use-agent: 爬虫的名称
  • Disallow: 不允许抓取的路径
  • Allow: 允许抓取的路径

示例

  • 禁止所有爬虫访问所有目录
    User-agent: *
    Disallow: /
  • 允许所有爬虫访问所有目录(也可以将robots.txt文件留作空白)
    User-agent: *
    Disallow:
  • 禁止所有爬虫访问某些目录
    User-agent: *
    Disallow: /private/
    Disallow: /tmp/
  • 只允许某一个爬虫访问所有目录
    User-agent: WebCrawler
    Disallow:
    User-agent: *
    Disallow: /

常见爬虫名称

爬虫名称 网站名称
BaiduSpider 百度
Googlebot 谷歌
360Sipder 360搜索
YodaoBot 有道
ia_archiver Alexa
Scooter altavista
Bingbot 必应

解析Robots协议

使用urllib.robotparser模块中的RobotFileParser类可以读取并解析Robots协议

RobotFileParser类有几个常用方法:

  • set_url: 设置robots.txt文件的链接, 如果在实例化RobotFileParser类的时候传入了url参数, 就不需要这个方法了
  • read: 读取robots.txt文件, 在解析之前必须先读取, 否则会解析失败, 全部返回False
  • parse: 解析robots.txt文件, 参数是文件中某些行的内容
  • can_fetch: 判断指定的User-agent是否能抓取指定的URL
  • mtime: 返回上次解析robots.txt文件的时间
  • modified: 设置解析robots.txt文件的时间
from urllib.robotparser import RobotFileParser

parser = RobotFileParser()
parser.set_url('https://www.baidu.com/robots.txt')
parser.read()  # 解析之前先读取
print(parser.can_fetch('Baiduspider', 'https://www.baidu.com'))  # True
print(parser.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))  # True
print(parser.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))  # False
上一篇:读书计划(持续更新中)


下一篇:Leetcode刷题(第5题)——最长回文子串