urllib 库 是 Python内置的 一个HTTP 请求库,虽然功能没有requests模块智能,但由于是内置的标准库,在一些简单的场景使用还是十分方便的和强大的。
它提供了以下几种模块:
urllib.request 请求模块(or opening and reading URLs)
urllib.error 异常处理模块(containing the exceptions raised by urllib.request)
urllib.parse url 解析模块(for parsing URLs)
urllib.robotparser 解析robots.txt 模块(for parsing robots.txt files)
HTTP请求Request
首先,我们来看一下最常用的requests模块, 用于模拟浏览器发起一个 HTTP 请求,参数如下:
1 2 3 4 5 6 7 8 9 10 11 12 urllib.request.urlopen(url, data=None , [timeout, ]*, cafile=None , capath=None , context=None ) urllib.request.Request(url, data=None , headers={}, origin_req_host=None , unverifiable=False , method=None )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import urllib.requestdef getPage (url ): page = urllib.request.urlopen(url) print(page) print(type(page)) print(page.status) print(page.getheaders()) return page.read().decode('utf-8' ) getPage("https://docs.python.org/3/library/urllib.html" ) getPage("https://cdn.pixabay.com/photo/2020/09/23/19/58/halloween-5596921__340.jpg" )
发现requests请求被禁止,此时我们需要使用Requests模拟一个请求头,如下:
1 2 3 4 5 6 7 8 9 10 11 headers = { 'USER-AGENT' :'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36' } def getPage (url ): page = urllib.request.Request(url, headers=headers) print(page) print(type(page)) page = urllib.request.urlopen(page) print(page.getheaders()) return page.read()
此时输入以上图片地址,输出为:
但此时爬取p站比如https://i.pximg.net/img-original/img/2020/10/28/00/04/57/85281729_p0.jpg仍然会是HTTP Error 403: Forbidden, 此时我们需要使用requests给p站增加Referer,参考carry_1024的文章:https://blog.csdn.net/ycarry2017/article/details/79599539
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import sslssl._create_default_https_context = ssl._create_unverified_context import urllib.requestopener = urllib.request.build_opener() opener.addheaders=[('Referer' , "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60541651" )] urllib.request.install_opener(opener) url = "https://i.pximg.net/img-original/img/2020/10/28/00/04/57/85281729_p0.jpg" urllib.request.urlretrieve(url,"D://acg-girl.jpg" ) headers = { 'USER-AGENT' :'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36' , 'Referer' : "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60541651" }
URL解析Parse
接下来我们来学习一下urllib的parse模块,该模块用于对网址进行非常方便的操作。
Parse module supports the following URL schemes:
file, ftp, gopher, hdl, http, https, imap, mailto, mms, news, nntp, prospero, rsync, rtsp, rtspu, sftp, shttp, sip, sips, snews, svn, svn+ssh, telnet, wais, ws, wss.
也就是几乎支持所有internet协议。
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 from urllib.parse import urlparse as prfrom urllib.parse import urlunparse as uprresult = pr('http://www.xiami.com/play?ids=/song/playlist/id/1/type/9#loadedt' ) result.hostname print(type(result),result) [print(result[i]) for i in range(len(result))] print(pr('www.xiami.com/play?ids=/song/playlist/id/1/type/9#loadedt' ,scheme="https" )) print(pr('https://www.xiami.com/play?ids=/song/playlist/id/1/type/9#loadedt' ,scheme="http" ,allow_fragments=False )) data = [result.scheme, result.netloc, result.path,result.params, result.query,result.fragment] print(upr(data))
urlsplit与urlparsel类似,但不包括params。
1 2 3 4 5 6 7 8 9 10 11 12 from urllib.parse import urlsplit as spfrom urllib.parse import urlunsplit as uspresult = sp('http://www.xiami.com/play?ids=/song/playlist/id/1/type/9#loadedt' ) print(type(result),result) data = [result.scheme, result.netloc, result.path, result.query,result.fragment] print(usp(data))
urljoin函数用于构造一个绝对url,当参数中的url为绝对路径的URL(即以//或scheme://开始),那么url的hostname和scheme将会出现在结果中
1 2 3 4 5 6 from urllib.parse import urljoin as jo print(jo("http://www.xiami.com/" ,"play?ids=/song/playlist/id/1/type/9#loadedt" )) print(jo("http://www.xiami.com/play?ids=/song/playlist/" ,"play?ids=/song/playlist/id/1/type/9#loadedt" )) print(jo("http:" ,"//www.xiami.com/play?ids=/song/playlist/id/1/type/9#loadedt" ))
其他,urlencode类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from urllib.parse import urlencode, parse_qs, quote, unquoteparams = { 'tn' :'baidu' , 'wd' : 'google chrome' , } base_url = 'http://www.baidu.com/s?' base_url + urlencode(params) print(parse_qs(urlencode(params))) 'https://www.baidu.com/s?wd=' + quote("百度" )url = 'https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6' print(unquote(url))
错误处理Error
我们对网页发起http请求时, 难免会遇到很多错误,比如404,连接超时,拒绝访问等,此时我们可以用urllib的error模块对异常进行处理。
异常处理主要用到两个类,urllib.error.URLError和urllib.error.HTTPError。
URLError 是 urllib.error 异常类的基类, 可以捕获由urllib.request 产生的异常。具有一个属性reason,即返回错误的原因。
1 2 3 4 5 6 7 8 9 10 import urllib.requestfrom urllib import errorurl = "https://www.google.com" try : response = request.urlopen(url) except error.URLError as e: print(e.reason)
HTTPError 专门处理 HTTP 和 HTTPS 请求的错误,有三个属性。
code:HTTP 请求返回的状态码。
reason:与父类用法一样,表示返回错误的原因。
headers:HTTP 请求返回的响应头信息。
1 2 3 4 5 6 7 8 9 10 11 12 from urllib import request,errorurl = "https://www.google.com" try : response = request.urlopen(url) except error.HTTPError as e: print('code: ' + e.code + '\n' ) print('reason: ' + e.reason + '\n' ) print('headers: ' + e.headers + '\n' ) ...
我们这里创建了一个getInfo函数,用于处理异常。
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 socketfrom urllib import request, parse, errorheaders = { 'User-Agent' :' Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' , 'Host' : 'httpbin.org' } dict = { 'words1' : 'you\'re a miracle' , 'words2' :'what do you fear' } def getInfo (url, data="" , headers={}, method="GET" ,timeout=1 ): try : dat = bytes(parse.urlencode(data), encoding='utf8' ) req = request.Request(url=url, data=dat, headers=headers, method=method) req = request.urlopen(req, timeout=timeout) print(req.read().decode('utf-8' )) except error.HTTPError as e: print(e.reason, e.code, e.headers, sep='\n' ) except error.URLError as e: if isinstance(e.reason, socket.timeout): print('TIME OUT' ) else : pass
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 getInfo("http://httpbin.org/post" ,dict,headers,"POST" ,5 ) getInfo('http://httpbin.org/get' ) getInfo('http://httpbin.org/get' ,timeout=.1 ) getInfo('http://httpbin.org/index.htm' )
Handler
如果我们需要在请求中添加代理proxy、处理Cookies,就需要用到Handler和OpenerDirector。
https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler
Handler 能处理请求(HTTP、HTTPS、FTP等)中的各种事情。其常见的类有:
HTTPDefaultErrorHandler:处理 HTTP 响应错误。
HTTPRedirectHandler:处理 HTTP 重定向。
HTTPCookieProcessor(cookiejar=None ):处理 HTTP 请求中的 Cookies
ProxyHandler(proxies=None ):设置代理
HTTPPasswordMgr:用于管理密码,它维护了用户名密码的表。
HTTPBasicAuthHandler:用于登录认证,一般和 HTTPPasswordMgr 结合使用。
HTTPPasswordMgrWithDefaultRealm
HTTPPasswordMgrWithPriorAuth
...
对于 OpenerDirector,之前用过 urlopen() 这个方法,实际上它就是 urllib 为我们提供的一个Opener。
opener 对象由 build_opener(handler) 方法来创建出来 。创建自定义的 opener,需要使用 install_opener(opener)方法。
使用代理Proxy
有些网站做了浏览频率限制或者禁止了你的IP请求。这个时候就需要我们使用代理来突破这“枷锁”,让对方服务器误以为我们是不同的用户发起的http请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from urllib.error import URLErrorfrom urllib.request import ProxyHandler, build_opener,install_openerurl = "http://tieba.baidu.com/" headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' } proxy_handler = ProxyHandler({ 'http' : 'web-proxy.oa.com:8080' , 'https' : 'web-proxy.oa.com:8080' }) opener = build_opener(proxy_handler) try : response = opener.open('https://www.baidu.com' ) print(response.read().decode('utf-8' )) except URLError as e: print(e.reason)
我们也可以使用requests模块进行更方便的proxy代理处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requests proxies = { 'http' : 'url' , 'https' : 'url' } proxies = { 'http' : 'socks5://user:password@host:port' , 'https' : 'socks5://user:password@host:port' } requests.get("https://www.baidu.com" , proxies=proxies)
认证登录Auth
有些网站需要登录之后才能继续浏览网页,这时就需要用到认证登录。
我们可以使用 HTTPPasswordMgrWithDefaultRealm() 实例化一个账号密码管理对象,然后使用 add_password() 函数添加账号和密码;接着使用 HTTPBasicAuthHandler() 得到 hander;再使用 build_opener() 获取 opener 对象;最后使用 opener 的 open() 函数发起请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_openerfrom urllib.error import URLErrorurl = "http://tieba.baidu.com/" user = 'user' password = 'password' pwdmgr = HTTPPasswordMgrWithDefaultRealm() pwdmgr.add_password(None , url, user, password) auth_handler = HTTPBasicAuthHandler(pwdmgr) 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)
Cookies设置
如果请求的页面每次需要身份验证,我们可以使用 Cookies 来自动登录,免去重复登录验证的操作。
获取 Cookies 需要使用 http.cookiejar.CookieJar() 实例化一个 Cookies 对象, 再用 urllib.request.HTTPCookieProcessor 构建出 handler 对象,最后使用 opener 的 open() 函数即可。
这个例子是获取请求百度的 Cookies 并保存到文件中,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import http.cookiejarimport urllib.requesturl = 'http://www.baidu.com' fileName = 'cookie.txt' cookie = http.cookiejar.CookieJar() handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open(url) print(response) for item in cookie: print(item.name+"=" +item.value) f = open(fileName,'a' ) for item in cookie: f.write(item.name + " = " + item.value + '\n' ) f.close()
也可使用 http.cookiejar.LWPCookieJar 或者http.cookiejar.MozillaCookieJar构建:
1 2 3 4 5 6 7 8 9 10 filename = 'cookies.txt' cookie = http.cookiejar.LWPCookieJar(filename) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open(url) cookie.save(ignore_discard=True , ignore_expires=True )
然后我们可以读取cookie文件:
1 2 3 4 5 6 7 8 9 cookie = http.cookiejar.LWPCookieJar() 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' ))
Robots
先列举一个robots.txt,以淘宝为例;
https://www.taobao.com/robots.txt
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 User-agent: Baiduspider Allow: /article Allow: /oshtml Disallow: /product/ Disallow: / User-Agent: Googlebot Allow: /article Allow: /oshtml Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Disallow: / User-agent: Bingbot Allow: /article Allow: /oshtml Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Disallow: / User-Agent: 360 Spider Allow: /article Allow: /oshtml Disallow: / User-Agent: Yisouspider Allow: /article Allow: /oshtml Disallow: / User-Agent: Sogouspider Allow: /article Allow: /oshtml Allow: /product Disallow: / User-Agent: Yahoo! Slurp Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Disallow: / User-Agent: * Disallow: /
解析robots.txt,我们需要用到RobotFileParser。
1 2 3 4 5 6 7 8 9 10 11 from urllib.robotparser import RobotFileParserfrom urllib.request import urlopenurl = "http://httpbin.org/robots.txt " rp = RobotFileParser(url) rp.read() print(rp.can_fetch('*' , 'http://httpbin.org/deny' )) print(rp.can_fetch('*' , "http://httpbin.org/image" ))
FAQ
HTTP Error 403: Forbidden
将请求加以包装,变成浏览器请求模式
1 2 3 headers = { 'USER-AGENT' :'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36' }
降低请求频率,每次请求时暂停短暂时间/用不同的IP进行访问
复制请求网址到浏览器进行检查,如仍然为403 Forbidden,检查请求网址是否有误或者过时。
参考:python 爬虫禁止访问解决方法(403): https://blog.csdn.net/u011808673/article/details/80609221
REFERENCES