《Python 3网络爬虫开发实战 》崔庆才著 第三章笔记
使用urllib
它是 Python內置的HTTP請求庫,也就是說不需要額外安裝即可使用。
包含以下4個模塊:
| request | 它是最基本的 HTTP請求模塊,可以用來模擬發送請求。就像在瀏覽器里輸入網址然后回車一樣,只需要給庫方法傳入URL以及額外的參數,就可以模擬實現這個過程了。 |
| error | 異常處理模塊,如果出現請求錯誤,我們可以捕獲這些異常,然后進行重試或其他操作以保證程序不會意外終止。 |
| parse | 一個工具模塊,提供了許多URL處理方法,比如拆分、解析、合并等。 |
| robotparser | 主要是用來識別網站的 robots.txt文件,然后判斷哪些網站可以爬,哪些網站不可以爬,它其實用得比較少。 |
重點講解下前3個模塊。
發送請求
使用urllib 的request模塊,我們可以方便地實現請求的發送并得到響應。
1.urlopen()
urllib.request模塊提供了最基本的構造HTTP請求的方法,利用它可以模擬瀏覽器的一個請求發起過程,同時它還帶有處理授權驗證(authenticaton)、重定向( redirection)、瀏覽器Cookies以及其他內容。
import urllib.request response=urllib.request.urlopen('https://www.python.org') print(response.read().decode('utf-8'))查看返回的類型
import urllib.request response=urllib.request.urlopen('https://www.python.org') print(type(response))發現是HTTPResponse類型
-
該對象包含了以下方法:
- read():返回網頁的內容。
- readinto()
- getheader(name)
- getheaders()
- fileno()
-
以及以下屬性:
- msg
- version
- status:請求頁面的狀態碼。
- reason
- debuglevel
- closed
得到這個對象之后,我們把它賦值為response變量,然后就可以調用這些方法和屬性,得到返回結果的一系列信息了。
urlopen API:
urllib.request.urlopen(url, data=None, [timeout, ]* , cafile=None , capath=None, cadefault=False, context=None)data參數
data參數是可選的。如果要添加該參數,并且如果它是字節流編碼格式的內容,即 bytes類型,則需要通過bytes()方法轉化。另外,如果傳遞了這個參數,則它的請求方式就不再是GET方式,而是POST方式。
import urllib.parse import urllib.request data=bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8') response = urllib.request.urlopen('http://httpbin.org/post', data=data) print(response.read())這里我們傳遞了一個參數 word,值是 hello。它需要被轉碼成 bytes(字節流)類型。其中轉字節流采用了bytes()方法,該方法的第一個參數需要是str(字符串)類型,需要用urllib.parse模塊里的urlencode()方法來將參數字典轉化為字符串;第二個參數指定編碼格式,這里指定為utf8。
timeout參數
timeout參數用于設置超時時間,單位為秒,意思就是如果請求超出了設置的這個時間,還沒有得到響應,就會拋出異常。如果不指定該參數,就會使用全局默認時間。它支持 HTTP、HTTPS、FTP請求。
import urllib.request response = urllib.request.urlopen('http://httpbin.org/get', timeout=1) print(response.read())這里我們設置超時時間是1秒。程序1秒過后,服務器依然沒有響應,于是拋出了URLError 異常該異常屬于urllib.error模塊,錯誤原因是超時。
因此,可以通過設置這個超時時間來控制一個網頁如果長時間未響應,就跳過它的抓取。這可以利用try except語句來實現,相關代碼如下:
import socket import urllib.request 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):print( 'TIME OUT')這里我們請求了http:/httpbin.org/get測試鏈接,設置超時時間是0.1秒,然后捕獲了URLError異常,接著判斷異常是socket.timeout類型(意思就是超時異常),從而得出它確實是因為超時而報錯,打印輸出了 TIME OUT。
其他參數
除了data參數和timeout參數外,還有context參數,它必須是ssl.SsLContext類型,用來指定SSL設置。
此外,cafile和 capath這兩個參數分別指定CA證書和它的路徑,這個在請求HTTPS鏈接時會有用。
cadefault參數現在已經棄用了,其默認值為False。
2.Request
我們知道利用urlopen()方法可以實現最基本請求的發起,但這幾個簡單的參數并不足以構建一個完整的請求。如果請求中需要加入Headers等信息,就可以利用更強大的Request類來構建。
import urllib.request request = urllib.request.Request( 'https://python.org') response = urllib.request.urlopen(request) print(response.read().decode( 'utf-8 '))下面我們看一下Request可以通過怎樣的參數來構造,它的構造方法如下:
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,我們可以通過修改它來偽裝瀏覽器。比如要偽裝火狐瀏覽器,你可以把它設置為:
Mozilla/5.0 (X11; U;Linux i686)Gecko/20071127 Firefox/2.0.0.11 -
第四個參數origin_req_host指的是請求方的host名稱或者IP地址。
-
第五個參數unverifiable表示這個請求是否是無法驗證的,默認是False,意思就是說用戶沒有足夠權限來選擇接收這個請求的結果。例如,我們請求一個 HTML文檔中的圖片,但是我們沒有自動抓取圖像的權限,這時unverifiable的值就是True。
-
第六個參數method是一個字符串,用來指示請求使用的方法,比如 GET、POST和 PUT等。
下面我們傳入多個參數構建請求來看一下:
from urllib import request, parse url = 'http://httpbin.org/post' headers = {'User-Agent':'Mozilla/4.o (compatible;MSIE 5.5; windows NT)' ,'Host':'httpbin.org' } dict = {'name':'Germey' }data = bytes(parse.urlencode(dict),encoding='utf8') req = request.Request(url=url, data=data,headers=headers,method='POST') response = request.urlopen(req) print(response.read().decode('utf-8'))headers也可以用add_header()方法來添加:
req = request.Request(url=url, data=data,method='POST') req.add_header( 'User-Agent', 'Mozilla/4.o (compatible; MSIE 5.5; Windows NT)')3.高級用法
在上面的過程中,我們雖然可以構造請求,但是對于一些更高級的操作(比如 Cookies處理、代理設置等),就需要更強大的工具 Handler 登場了。簡而言之,我們可以把它理解為各種處理器,有專門處理登錄驗證的,有處理Cookies的,有處理代理設置的。利用它們,我們幾乎可以做到 HTTP請求中所有的事情。
首先,介紹一下urllib.request模塊里的BaseHandler類,它是所有其他Handler的父類,它提供了最基本的方法,例如 default_open()、 protocol_request()等。有各種 Handler子類繼承這個BaseHandler類,舉例如下。
| HTTPDefaultErrorHandler | 用于處理HTTP響應錯誤,錯誤都會拋出 HTTPError類型的異常。 |
| HTTPRedirectHandler | 用于處理重定向。 |
| HTTPCookieProcessor | 用于處理Cookies。 |
| ProxyHandler | 用于設置代理,默認代理為空。 |
| HTTPPasswordMgr | 用于管理密碼,它維護了用戶名和密碼的表。 |
| HTTPBasicAuthHandler | 用于管理認證,如果一個鏈接打開時需要認證,那么可以用它來解決認證問題。 |
另一個比較重要的類就是OpenerDirector,我們可以稱為Opener。Opener可以使用open()方法,返回的類型和urlopen()如出一轍。它就是利用Handler來構建Opener。下面用幾個實例來看看它們的用法。
-
驗證
有些網站在打開時就會彈出提示框,直接提示你輸入用戶名和密碼,驗證成功后才能查看頁面。
借助HTTPBasicAuthHandler就可以完成
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener from urllib.error import URLErrorusername = 'username' password = 'password' url = 'http://localhost:5000/' 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對象,其參數是HTTPPasswordMgrWithDefaultRealm對象,它利用add_password()添加進去用戶名和密碼,這樣就建立了一個處理驗證的 Handler。
接下來,利用這個Handler并使用build_opener()方法構建一個Opener,這個Opener在發送請求時就相當于已經驗證成功了。
接下來,利用Opener的open()方法打開鏈接,就可以完成驗證了。這里獲取到的結果就是驗證后的頁面源碼內容。
-
代理
在做爬蟲的時候,免不了要使用代理,如果要添加代理,可以這樣做:
from urllib.error import URLError from urllib.request import ProxyHandler,build_openerproxy_handler = ProxyHandler({'http': 'http://127.0.0.1:9743','https ': 'https://127.0.0.1:9743' })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)這里我們在本地搭建了一個代理,它運行在9743端口上。
這里使用了ProxyHandler,其參數是一個字典,鍵名是協議類型(比如HTTP或者HTTPS等).鍵值是代理鏈接,可以添加多個代理。
然后,利用這個Handler 及 build opener()方法構造一個Opener,之后發送請求即可。
-
Cookies
Cookies的處理就需要相關的Handler 了。我們先用實例來看看怎樣將網站的Cookies獲取下來,相關代碼如下:
import http.cookiejar,urllib.requestcookie = http.cookiejar.CookieJar() handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open( ' http://wwwl.baidu.com')for item in cookie:print(item.name+"="+item.value)首先,我們必須聲明一個CookieJar對象。接下來,就需要利用HTTPCookieProcessor來構建一個Handler,最后利用build_opener()方法構建出Opener,執行open()函數即可。運行結果如下:
我們還可以輸出為文件格式:
import http.cookiejar,urllib.requestfilename = "cookies.txt" cookie = http.cookiejar.MozillaCookieJar(filename) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener. open('http://www.baidu.com' )cookie.save(ignore_discard=True,ignore_expires=True)這時CookieJar就需要換成MozillaCookieJar,它在生成文件時會用到,是CookieJar的子類,可以用來處理Cookies 和文件相關的事件,比如讀取和保存Cookies,可以將Cookies保存成Mozilla型瀏覽器的 Cookies格式。運行之后,可以發現生成了一個cookies.txt文件.其內容如下:
另外,LWPCookieJar同樣可以讀取和保存Cookies,但是保存的格式和MozillaCookieJar不一樣,它會保存成libwww-perl(LWP)格式的Cookies文件。要保存成LWP格式的Cookies文件,可以在聲明時就改為:
cookie = http.cookiejar.LWPCookieJar(filename)可以發現他們之間的差別有點大。
下面我們以LWPCookieJar格式來看看如何使用cookie文件
import http.cookiejar,urllib.request 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'))可以看到,這里調用load()方法來讀取本地的Cookies文件,獲取到了Cookies的內容。不過前提是我們首先生成了LWPCookieJar格式的Cookies,并保存成文件,然后讀取 Cookies之后使用同樣的方法構建Handler和 Opener即可完成操作。
運行結果正常的話,會輸出百度網頁的源代碼。
通過上面的方法,我們可以實現絕大多數請求功能的設置了。
處理異常
urllib的error模塊定義了由request模塊產生的異常。如果出現了問題,request模塊便會拋出error模塊中定義的異常。
1.URLError
URLError類來自urllib庫的error模塊,它繼承自OSError類,是error異常模塊的基類,由request模塊生的異常都可以通過捕獲這個類來處理。
它具有一個屬性reason,即返回錯誤的原因。
from urllib import request,error try:response=request.urlopen('https://cuiqingcai.com/index.htm') except error.URLError as e:print(e.reason)程序沒有直接報錯,而是輸出了如上內容,這樣通過如上操作,我們就可以避免程序異常終止,同時異常得到了有效處理。
2.HTTPError
它是URLError的子類,專門用來處理IITTP請求錯誤,比如認證請求失敗等。它有如下3個屬性。
| code | 返回HTTP狀態碼,比如404表示網頁不存在,500表示服務器內部錯誤等。 |
| reason | 同父類一樣,用于返回錯誤的原因。 |
| headers | 返回請求頭。 |
依然是同樣的網址,這里捕獲了HTTPError異常,輸出了reason、code和 headers屬性。
因為URLError是 HTTPError的父類,所以可以先選擇捕獲子類的錯誤,再去捕獲父類的錯誤
有時候,reason屬性返回的不一定是字符串,也可能是一個對象。
import socket import urllib.request import urllib.errortry:response=urllib.request.urlopen('https://www.baidu.com',timeout=0.01) except urllib.error.URLError as e:print(type(e.reason))if isinstance(e.reason,socket.timeout):print('TIME OUT')可以發現,reason屬性的結果是socket.timeout類。所以,這里我們可以用isinstance()方法來判斷它的類型,作出更詳細的異常判斷。
解析鏈接
urllib 庫里還提供了 parse 模塊,它定義了處理 URL 的標準接口,例如實現 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。
1.urlparse()
該方法可以實現 URL 的識別和分段
from urllib.parse import urlparseresult = urlparse('https://www.baidu.com/index.html;user?id=5#comment') print(type(result)) print(result)這里我們利用urlparse()方法進行了一個URL 的解析。首先,輸出了解析結果的類型,然后將結果也輸出出來。
可以看到,返回結果是一個ParseResult類型的對象,它包含6個部分,分別是scheme,netloc,path,params,query和 fragment。一般的URL標準格式如下:
scheme://netloc/path;params?query#fragment| scheme | 😕/前面的就是scheme,代表協議 | https |
| netloc | 第一個/符號前面就是netloc,即域名 | www.baidu.com |
| path | netloc后面就是path,即訪問路徑 | /index.html |
| params | 分號前面是params,代表參數 | user |
| query | 參數后面是查詢條件query,一般作GET類型的URI | id=5 |
| fragment | 井號#后面是錨點,用于直接定位頁面內部的下拉位置 | comment |
urlparse參數如下:
urlparse(url=,scheme=,allow_fragments=)- url:這是必填項,即待解析的URL。
- schema:這是默認的協議(HTTP、HTTPS等),默認為HTTPS。
- allow_fragments:是否忽略fragment,默認為True,即不忽視fragment。當URL中不包含params和 query時,fragment便會被解析為path的一部分。
urlparse的返回結果是一個元組,我們可以用索引順序來獲取,也可以用屬性名獲取。
from urllib.parse import urlparseresult = urlparse('https://www.baidu.com/index.html;user?id=5#comment') print(result[0]) print(result.scheme)2.urlunparse()
有了urlparse(),相應地就有了它的對立方法urlunparse()。它接受的參數是一個可迭代對象,但是它的長度必須是6,否則會拋出參數數量不足或者過多的問題。先用一個實例看一下:
from urllib.parse import urlunparsedata=['http','www.baidu.com','index.html','user','a=6','comment'] print(urlunparse(data))這里參數data用了列表類型。當然,你也可以用其他類型,比如元組或者特定的數據結構。
觀察結果可以發現,我們成功構造了url。
3.urlsplit()
這個方法和 urlparse()方法非常相似,只不過它不再單獨解析params這一部分,只返回5個結果。上面例子中的params會合并到path 中。示例如下:
from urllib.parse import urlsplitresult=urlsplit('http://www.baidu.com/index.html;user?id=5#comment') print(result)可以發現,返回結果是SplitResult,它其實也是一個元組類型,既可以用屬性獲取值,也可以用索引來獲取。
4.urlunsplit()
與urlunparse()類似,它也是將鏈接各個部分組合成完整鏈接的方法,傳入的參數也是一個可迭代對象,例如列表、元組等,唯一的區別是長度必須為5。示例如下:
from urllib.parse import urlunparsedata=['http','www.baidu.com','index.html','user','a=6','comment'] print(urlunparse(data))5.urljoin()
生成鏈接還有另一個方法,那就是urljoin()方法。我們可以提供一個base_url(基礎鏈接)作為第一個參數,將新的鏈接作為第二個參數,該方法會分析base_url的 scheme、netloc和path這3個內容并對新鏈接缺失的部分進行補充,最后返回結果。
from urllib.parse import urljoinprint(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#comment', '?category=2'))可以發現,base_url 提供了三項內容 scheme、netloc 和 path。如果這 3 項在新的鏈接里不存在,就予以補充;如果新的鏈接存在,就使用新的鏈接的部分。而 base_url 中的 params、query 和 fragment 是不起作用的。
通過 urljoin 方法,我們可以輕松實現鏈接的解析、拼合與生成。
6.urlencode()
這個方法非常常用。有時為了更加方便地構造參數,我們會事先用字典來表示。要轉化為URL的參數時,只需要調用該方法即可。
from urllib.parse import urlencodeparams={'name': 'germey','age': 22 }base_url='http://www.baidu.com?' url=base_url+urlencode(params) print(url)這里首先聲明了一個字典來將參數表示出來,然后調用urlencode()方法將其序列化為GET請求參數。
可以看到,參數就成功地由字典類型轉化為GET請求參數了。
7.parse_qs()
有了序列化,必然就有反序列化。如果我們有一串 GET 請求參數,利用 parse_qs()方法,就可以將它轉回字典,示例如下:
from urllib.parse import parse_qsquery = 'name=germey&age=25' print(parse_qs(query))可以看到,這樣就成功轉回為字典類型了。
8.parse_sql()
還有一個 parse_qsl 方法,它用于將參數轉化為元組組成的列表,示例如下:
from urllib.parse import parse_qslquery = 'name=germey&age=25' print(parse_qsl(query))可以看到,運行結果是一個列表,而列表中的每一個元素都是一個元組,元組的第一個內容是參數名,第二個內容是參數值。
9.quote()
該方法可以將內容轉化為 URL 編碼的格式。URL 中帶有中文參數時,有時可能會導致亂碼的問題,此時可以用這個方法可以將中文字符轉化為 URL 編碼,示例如下:
from urllib.parse import quotekeyword = '壁紙' url = 'https://www.baidu.com/s?wd=' + quote(keyword) print(url)10.unquote()
它可以進行 URL 解碼,示例如下:
from urllib.parse import unquoteurl = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8' print(unquote(url))本節中,我們介紹了 parse 模塊的一些常用 URL 處理方法。有了這些方法,我們可以方便地實現 URL 的解析和構造,建議熟練掌握。
分析Robots協議
利用 urllib 的 robotparser 模塊,我們可以實現網站 Robots 協議的分析。本節中,我們來簡單了解一下該模塊的用法。
1.Robots協議
Robots 協議也稱作爬蟲協議、機器人協議,它的全名叫作網絡爬蟲排除標準(Robots Exclusion Protocol),用來告訴爬蟲和搜索引擎哪些頁面可以抓取,哪些不可以抓取。它通常是一個叫作 robots.txt 的文本文件,一般放在網站的根目錄下。
當搜索爬蟲訪問一個站點時,它首先會檢查這個站點根目錄下是否存在 robots.txt 文件,如果存在,搜索爬蟲會根據其中定義的爬取范圍來爬取。如果沒有找到這個文件,搜索爬蟲便會訪問所有可直接訪問的頁面。
下面我們看一個 robots.txt 的樣例:
User-agent: * <!-- 該協議對任何爬取爬蟲有效 --> Disallow: / <!-- 不允許抓取所有頁面。 --> Allow: /public/ <!-- 可以抓取 public 目錄 -->這實現了對所有搜索爬蟲只允許爬取 public 目錄的功能,將上述內容保存成 robots.txt 文件,放在網站的根目錄下,和網站的入口文件(比如 index.php、index.html 和 index.jsp 等)放在一起。
2.爬蟲名稱
一些常見搜索爬蟲的名稱及其對應的網站。
| BaiduSpider | 百度 | www.baidu.com |
| Googlebot | 谷歌 | www.google.com |
| 360Spider | 360 搜索 | www.so.com |
| YodaoBot | 有道 | www.youdao.com |
| ia_archiver | Alexa | www.alexa.cn |
| Scooter | altavista | www.altavista.com |
| Bingbot | 必應 | www.bing.com |
3.robotparser
我們就可以使用 robotparser 模塊來解析 robots.txt 了。該模塊提供了一個類 RobotFileParser,它可以根據某網站的 robots.txt 文件來判斷一個爬蟲是否有權限來爬取這個網頁。
該類用起來非常簡單,只需要在構造方法里傳入 robots.txt 的鏈接即可。也可以在聲明時不傳入,默認為空,最后再使用 set_url 方法設置一下即可。
urllib.robotparser.RobotFileParser(url='')這個類常用的幾個方法:
- set_url():用來設置 robots.txt 文件的鏈接。如果在創建 RobotFileParser 對象時傳入了鏈接,那么就不需要再使用這個方法設置了。
- read():讀取 robots.txt 文件并進行分析。注意,這個方法執行一個讀取和分析操作,如果不調用這個方法,接下來的判斷都會為 False,所以一定記得調用這個方法。這個方法不會返回任何內容,但是執行了讀取操作。
- parse():用來解析 robots.txt 文件,傳入的參數是 robots.txt 某些行的內容,它會按照 robots.txt 的語法規則來分析這些內容。
- can_fetch():該方法用兩個參數,第一個是 User-Agent,第二個是要抓取的 URL。返回的內容是該搜索引擎是否可以抓取這個 URL,返回結果是 True 或 False。
- mtime():返回的是上次抓取和分析 robots.txt 的時間,這對于長時間分析和抓取的搜索爬蟲是很有必要的,你可能需要定期檢查來抓取最新的 robots.txt。
- modified():它同樣對長時間分析和抓取的搜索爬蟲很有幫助,將當前時間設置為上次抓取和分析 robots.txt 的時間。
接下來我們查看例子:
from urllib.robotparser import RobotFileParserrp = RobotFileParser() # rp = RobotFileParser('https://www.baidu.com/robots.txt') 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/'))使用requests
使用 urllib 處理網頁驗證和 Cookie 時,需要寫 Opener 和 Handler 來處理。另外我們要實現 POST、PUT 等請求時寫法也不方便。
為了更加方便地實現這些操作,就有了更為強大的庫 requests,有了它,Cookie、登錄驗證、代理設置等操作都不是事兒。
基本用法
1.示例
urllib 庫中的 urlopen 方法實際上是以 GET 方式請求網頁,而 requests 中相應的方法就是 get 方法:
import requests r=requests.get('https://www.baidu.com') print(type(r)) print(r.status_code) print(type(r.text)) print(r.text) print(r.cookies)除了get方法外,還有其他的請求方法:
import requestsr = requests.get('https://httpbin.org/get') r = requests.post('https://httpbin.org/post') r = requests.put('https://httpbin.org/put') r = requests.delete('https://httpbin.org/delete') r = requests.patch('https://httpbin.org/patch')2.GET請求
GET請求如何攜帶參數?
一種方法是,我們可以直接在url中附加即可,如:
r=requests.get('http://httpbin.org/get?name=germey&age=20')還可以使用requests的params這個參數,如:
import requestsdata = {'name': 'germey','age': 25 } r = requests.get('https://httpbin.org/get', params=data) print(r.text)網頁的返回類型實際上是 str 類型,但是它很特殊,是 JSON 格式的。所以,如果想直接解析返回結果,得到一個 JSON 格式的數據的話,可以直接調用 json 方法。如:
import requestsr = requests.get('https://httpbin.org/get') print(type(r.text)) print(r.json()) print(type(r.json()))可以發現,調用 json 方法,就可以將返回結果是 JSON 格式的字符串轉化為字典。
但需要注意的是,如果返回結果不是 JSON 格式,便會出現解析錯誤,拋出 json.decoder.JSONDecodeError 異常。
抓取網頁
import requests import rer = requests.get('https://ssr1.scrape.center/') pattern = re.compile('<h2.*?>(.*?)</h2>', re.S) titles = re.findall(pattern, r.text) print(titles)抓取二進制數據
在上面的例子中,我們抓取的是網站的一個頁面,實際上它返回的是一個 HTML 文檔。
圖片、音頻、視頻這些文件本質上都是由二進制碼組成的,由于有特定的保存格式和對應的解析方式,我們才可以看到這些形形色色的多媒體。所以,想要抓取它們,就要拿到它們的二進制數據。
如果直接使用抓取頁面的方法抓取二進制文件,會出現亂碼的情況。
import requestsr=requests.get("https://www.baidu.com/favicon.ico") with open('favicon.ico','wb')as f:f.write(r.content)這里用了open()方法,它的第一個參數是文件名稱,第二個參數代表以二進制寫的形式打開,可以向文件里寫入二進制數據。
運行結束之后,可以發現在文件夾中出現了名為favicon.ico 的圖標。
同樣,其他二進制文件也可以這樣子來進行爬取。
添加headers
import requestsheaders = {'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.baidu.com/', headers=headers) print(r.text)3.POST請求
實例如下:
import requestsdata={'name': 'germey','age': '22'} r=requests.post("http://httpbin.org/post",data=data) print(r.text)4.響應
發送請求后,得到的自然就是響應。除了前面使用到的text和content之外,還有狀態碼、響應頭、Cookies等。
import requestsr = requests.get('https://ssr1.scrape.center/') 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)這里分別打印輸出 status_code 屬性得到狀態碼,輸出 headers 屬性得到響應頭,輸出 cookies 屬性得到 Cookie,輸出 url 屬性得到 URL,輸出 history 屬性得到請求歷史。
高級用法
1.文件上傳
import requestsfiles={'file': open('favicon.ico','rb')} r=requests.post("http://httpbin.org/post",files=files) print(r.text)2.Cookies
import requestsr=requests.get("https://www.baidu.com") print(r.cookies) for key,value in r.cookies.items():print(key+'='+value)這里我們首先調用 cookies 屬性即可成功得到 Cookie,可以發現它是 RequestCookieJar 類型。然后用 items 方法將其轉化為元組組成的列表,遍歷輸出每一個 Cookie 條目的名稱和值,實現 Cookie 的遍歷解析。
我們還可以往headers里面添加Cookie,實現登錄。
import requestsheaders = {'Cookie': '自己的Cookie值','User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36', } r = requests.get('https://github.com/', headers=headers) print(r.text)3.會話維持
在 requests 中,如果直接利用 get 或 post 等方法的確可以做到模擬網頁的請求,但是這實際上是相當于不同的 Session,也就是說相當于你用了兩個瀏覽器打開了不同的頁面。
其實解決這個問題的主要方法就是維持同一個 Session,也就是相當于打開一個新的瀏覽器選項卡而不是新開一個瀏覽器。但是我又不想每次設置 Cookies,那該怎么辦呢?這時候就有了新的利器 Session 對象。Session 在平常用得非常廣泛,可以用于模擬在一個瀏覽器中打開同一站點的不同頁面。
import requestss = requests.Session() s.get('https://httpbin.org/cookies/set/number/123456789') r = s.get('https://httpbin.org/cookies') print(r.text)利用 Session,可以做到模擬同一個會話而不用擔心 Cookie 的問題。它通常用于模擬登錄成功之后再進行下一步的操作。
4.SSL證書驗證
現在很多網站都要求使用 HTTPS 協議,但是有些網站可能并沒有設置好 HTTPS 證書,或者網站的 HTTPS 證書可能并不被 CA 機構認可,這時候,這些網站可能就會出現 SSL 證書錯誤的提示。
如果我們一定要爬取這個網站怎么辦呢?我們可以使用 verify 參數控制是否驗證證書,如果將其設置為 False,在請求時就不會再驗證證書是否有效。如果不加 verify 參數的話,默認值是 True,會自動驗證。因此我們將verify參數設為False即可。
import requestsresponse = requests.get('https://ssr2.scrape.center/', verify=False) print(response.status_code)但是報了一個警告,它建議我們給它指定證書。我們可以對警告進行忽略。
import requests from requests.packages import urllib3urllib3.disable_warnings() response = requests.get('https://ssr2.scrape.center/', verify=False) print(response.status_code)我們還可以導入證書進行訪問,注意,私有證書的key必須是解密狀態,加密狀態的key是不支持的。
import requestsresponse=requests.get('https://www.12306.cn',cert=('/path/server.crt','/path/key')) print(response.status_code)5.代理設置
一旦開始大規模爬取,對于大規模且頻繁的請求,網站可能會彈出驗證碼,或者跳轉到登錄認證頁面,更甚者可能會直接封禁客戶端的 IP,導致一定時間段內無法訪問。為了防止這種情況,我們需要設置代理來解決這個問題,這就需要用到 proxies 參數。
import requestsproxies={"http": "http://10.10.1.10.:3128","https": "https://10.10.1.10:1080", }requests.get("https://www.taobao.com",proxies=proxies)除了基本的HTTP代理意外,還有SOCKS協議代理。
import requestsproxies = {'http': 'socks5://user:password@host:port','https': 'socks5://user:password@host:port' } requests.get('https://httpbin.org/get', proxies=proxies)6.超時設置
為了防止服務器不能及時響應,應該設置一個超時時間,即超過了這個時間還沒有得到響應,那就報錯。這需要用到 timeout 參數。這個時間的計算是發出請求到服務器返回響應的時間。
import requestsr = requests.get('https://httpbin.org/get', timeout=1) print(r.status_code)7.身份認證
這個網站就是啟用了基本身份認證,在上一節中我們可以利用 urllib 來實現身份的校驗,但實現起來相對繁瑣。
我們可以使用 requests 自帶的身份認證功能,通過 auth 參數即可設置。這個示例網站的用戶名和密碼都是 admin,在這里我們可以直接設置。
import requests from requests.auth import HTTPBasicAuthr = requests.get('https://ssr3.scrape.center/', auth=HTTPBasicAuth('admin', 'admin')) print(r.status_code)如果參數都傳一個 HTTPBasicAuth 類,就顯得有點煩瑣了,所以 requests 提供了一個更簡單的寫法,可以直接傳一個元組,它會默認使用 HTTPBasicAuth 這個類來認證。
import requestsr = requests.get('https://ssr3.scrape.center/', auth=('admin', 'admin')) print(r.status_code)8.Prepared Request
實際上,requests 在發送請求的時候,是在內部構造了一個 Request 對象,并給這個對象賦予了各種參數,包括 url、headers、data 等等,然后直接把這個 Request 對象發送出去,請求成功后會再得到一個 Response 對象,再解析即可。Response 對象實際上就是 Prepared Request。
from requests import Request, Sessionurl = 'https://httpbin.org/post' data = {'name': 'germey'} headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 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 方法發送。
正則表達式
基礎
對于 URL 來說,可以用下面的正則表達式匹配:
[a-zA-z]+://[^\s]*這個正則表達式去匹配一個字符串,如果這個字符串中包含類似 URL 的文本,就會被提取出來。
比如,a-z 代表匹配任意的小寫字母,\s 表示匹配任意的空白字符,* 就代表匹配前面的字符任意多個,這一長串的正則表達式就是這么多匹配規則的組合。
常見的匹配規則
| \w | 匹配字母、數字及下劃線 |
| \W | 匹配不是字母、數字及下劃線的字符 |
| \s | 匹配任意空白字符,等價于 [\t\n\r\f] |
| \S | 匹配任意非空字符 |
| \d | 匹配任意數字,等價于 [0-9] |
| \D | 匹配任意非數字的字符 |
| \A | 匹配字符串開頭 |
| \Z | 匹配字符串結尾,如果存在換行,只匹配到換行前的結束字符串 |
| \z | 匹配字符串結尾,如果存在換行,同時還會匹配換行符 |
| \G | 匹配最后匹配完成的位置 |
| \n | 匹配一個換行符 |
| \t | 匹配一個制表符 |
| ^ | 匹配一行字符串的開頭 |
| $ | 匹配一行字符串的結尾 |
| . | 匹配任意字符,除了換行符,當 re.DOTALL 標記被指定時,則可以匹配包括換行符的任意字符 |
| […] | 用來表示一組字符,單獨列出,比如 [amk] 匹配 a、m 或 k |
| [^…] | 不在 [] 中的字符,比如 匹配除了 a、b、c 之外的字符 |
| * | 匹配 0 個或多個表達式 |
| + | 匹配 1 個或多個表達式 |
| ? | 匹配 0 個或 1 個前面的正則表達式定義的片段,非貪婪方式 |
| {n} | 精確匹配 n 個前面的表達式 |
| {n, m} | 匹配 n 到 m 次由前面正則表達式定義的片段,貪婪方式 |
| a|b | 匹配a或b |
| () | 匹配括號內的表達式,也表示一個組 |
match()
match 方法會嘗試從字符串的起始位置匹配正則表達式,如果匹配,就返回匹配成功的結果;如果不匹配,就返回 None。示例如下:
import recontent = 'Hello 123 4567 World_This is a Regex Demo' print(content) print(len(content)) result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content) print(result) print(result.group()) print(result.span())開頭的 ^ 是匹配字符串的開頭,也就是以 Hello 開頭;然后 \s 匹配空白字符,用來匹配目標字符串的空格;\d 匹配數字,3 個 \d 匹配 123;然后再寫 1 個 \s 匹配空格;后面還有 4567,我們其實可以依然用 4 個 \d 來匹配,但是這么寫比較煩瑣,所以后面可以跟 {4} 以代表匹配前面的規則 4 次,也就是匹配 4 個數字;后面再緊接 1 個空白字符,最后的 \w{10} 匹配 10 個字母及下劃線。
在 match 方法中,第一個參數傳入了正則表達式,第二個參數傳入了要匹配的字符串。
打印輸出結果,可以看到結果是 SRE_Match 對象,這證明成功匹配。該對象有兩個方法:
- group 方法可以輸出匹配到的內容,結果是 Hello 123 4567 World_This,這恰好是正則表達式規則所匹配的內容。
- span 方法可以輸出匹配的范圍,結果是 (0, 25),這就是匹配到的結果字符串在原字符串中的位置范圍。
1.匹配目標
如果想從字符串中提取一部分內容,該怎么辦呢?就像最前面的實例一樣,從一段文本中提取出郵件或電話號碼等內容。
可以使用括號 () 將想提取的子字符串括起來。() 實際上標記了一個子表達式的開始和結束位置,被標記的每個子表達式會依次對應每一個分組,調用 group 方法傳入分組的索引即可獲取提取的結果。示例如下:
import recontent = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^Hello\s(\d+)\sWorld', content) print(content) print(result) print(result.group()) print(result.group(1)) print(result.span())這里我們想把字符串中的 1234567 提取出來,此時可以將數字部分的正則表達式用 () 括起來,然后調用了 group(1) 獲取匹配結果。
我們成功得到了 1234567。這里用的是 group(1),它與 group() 有所不同,后者會輸出完整的匹配結果,而前者會輸出第一個被 () 包圍的匹配結果。假如正則表達式后面還有 () 包括的內容,那么可以依次用 group(2)、group(3) 等來獲取。
2.通用匹配
前面我們寫的正則表達式其實比較復雜,出現空白字符我們就寫 \s 匹配,出現數字我們就用 \d 匹配,這樣的工作量非常大。其實完全沒必要這么做,因為還有一個萬能匹配可以用,那就是 .*。其中 . 可以匹配任意字符(除換行符),* 代表匹配前面的字符無限次,所以它們組合在一起就可以匹配任意字符了。有了它,我們就不用挨個字符匹配了。
import recontent = 'Hello 123 4567 World_This is a Regex Demo' result = re.match('^Hello.*Demo$', content) print(content) print(result) print(result.group()) print(result.span())因此,我們可以使用 .* 簡化正則表達式的書寫。
3.貪婪與非貪婪
使用上面的通用匹配 .* 時,可能有時候匹配到的并不是我們想要的結果。看下面的例子:
import recontent = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^He.*(\d+).*Demo$', content) print(content) print(result) print(result.group(1))我們依然想獲取中間的數字,所以中間依然寫的是 (\d+)。而數字兩側由于內容比較雜亂,所以想省略來寫,都寫成 .*。最后,組成 ^He.*(\d+).*Demo$,看樣子并沒有什么問題。我們看下運行結果:
可以發現最終匹配到的結果是一個數字7。這里就涉及貪婪匹配與非貪婪匹配的問題。
在貪婪匹配下,.* 會匹配盡可能多的字符。正則表達式中 .* 后面是 \d+,也就是至少一個數字,并沒有指定具體多少個數字,因此,.* 就盡可能匹配多的字符,這里就把 123456 匹配了,給 \d+ 留下一個可滿足條件的數字7,最后得到的內容就只有數字7了。
這里只需要使用非貪婪匹配就好了。非貪婪匹配的寫法是 .*?,多了一個 ?,那么它可以達到怎樣的效果?我們再用實例看一下:
import recontent = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^He.*?(\d+).*Demo$', content) print(content) print(result) print(result.group(1))此時就可以成功獲取 1234567 了。貪婪匹配是盡可能匹配多的字符,非貪婪匹配就是盡可能匹配少的字符。當 .*? 匹配到 Hello 后面的空白字符時,再往后的字符就是數字了,而 \d+ 恰好可以匹配,那么這里 .*? 就不再進行匹配,交給 \d+ 去匹配后面的數字。所以這樣 .*? 匹配了盡可能少的字符,\d+ 的結果就是 1234567 了。
因此,在做匹配的時候,字符串中間盡量使用非貪婪匹配,也就是用 .*? 來代替 .*,以免出現匹配結果缺失的情況。
import recontent = 'http://weibo.com/comment/kEraCN' result1 = re.match('http.*?comment/(.*?)', content) result2 = re.match('http.*?comment/(.*)', content) print(content) print('result1', result1.group(1)) print('result2', result2.group(1))可以觀察到,.*? 沒有匹配到任何結果,而 .* 則盡量匹配多的內容,成功得到了匹配結果。
4.修飾符
.*是用來匹配除換行符之外的任意符號,因此在遇到換行符的時候會報錯,因此我們需要添加修飾符re.S進行修正,如:
result = re.match('^He.*?(\d+).*?Demo$', content, re.S)這個 re.S 在網頁匹配中經常用到。因為 HTML 節點經常會有換行,加上它,就可以匹配節點與節點之間的換行了。
修飾符及其描述:
| re.I | 使匹配對大小寫不敏感 |
| re.L | 做本地化識別(locale-aware)匹配 |
| re.M | 多行匹配,影響 ^ 和 $ |
| re.S | 使。匹配包括換行符在內的所有字符 |
| re.U | 根據 Unicode 字符集解析字符。這個標志影響 \w、\W、\b 和 \B |
| re.X | 該標志通過給予你更靈活的格式以便你將正則表達式寫得更易于理解 |
在網頁匹配中,較為常用的有 re.S 和 re.I。
5.轉義匹配
我們知道正則表達式定義了許多匹配模式,如 . 匹配除換行符以外的任意字符,但是如果目標字符串里面就包含 .,那該怎么辦呢?
這里就需要用到轉義匹配了,示例如下:
import recontent = '(百度) www.baidu.com' result = re.match('\(百度\) www\.baidu\.com', content) print(result)當遇到用于正則匹配模式的特殊字符時,在前面加反斜線轉義一下即可。例如可以用 \. 來匹配 .,運行結果如下:
可以發現成功匹配到了原字符串。
search()
前面提到過,match 方法是從字符串的開頭開始匹配的,一旦開頭不匹配,那么整個匹配就失敗了。我們看下面的例子:
import recontent = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings' result = re.match('Hello.*?(\d+).*?Demo', content) print(result)這里的字符串以 Extra 開頭,但是正則表達式以 Hello 開頭,整個正則表達式是字符串的一部分,但是這樣匹配是失敗的。運行結果如下:
因為 match 方法在使用時需要考慮到開頭的內容,這在做匹配時并不方便。它更適合用來檢測某個字符串是否符合某個正則表達式的規則。
這里就有另外一個方法 search,它在匹配時會掃描整個字符串,然后返回第一個成功匹配的結果。也就是說,正則表達式可以是字符串的一部分,在匹配時,search 方法會依次掃描字符串,直到找到第一個符合規則的字符串,然后返回匹配內容,如果搜索完了還沒有找到,就返回 None。
import recontent = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings' result = re.search('Hello.*?(\d+).*?Demo', content) print(result)下面再用幾個實例來看看 search 方法的用法。
這里有一段待匹配的 HTML 文本,接下來寫幾個正則表達式實例來實現相應信息的提取:
<div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div>首先,我們嘗試提取 class 為 active 的 li 節點內部的超鏈接包含的歌手名和歌名,此時需要提取第三個 li 節點下 a 節點的 singer 屬性和文本。
此時正則表達式可以以 li 開頭,然后尋找一個標志符 active,中間的部分可以用 .*? 來匹配。接下來,要提取 singer 這個屬性值,所以還需要寫入 singer="(.*?)",這里需要提取的部分用小括號括起來,以便用 group 方法提取出來,它的兩側邊界是雙引號。然后還需要匹配 a 節點的文本,其中它的左邊界是 >,右邊界是 </a>。然后目標內容依然用 (.*?) 來匹配,所以最后的正則表達式就變成了:
<li.*?active.*?singer="(.*?)">(.*?)</a>然后再調用 search 方法,它會搜索整個 HTML 文本,找到符合正則表達式的第一個內容返回。另外,由于代碼有換行,所以這里第三個參數需要傳入 re.S。整個匹配代碼如下:
import re html = ''' <div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div> ''' result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S) if result:print(result.group(1), result.group(2))由于需要獲取的歌手和歌名都已經用小括號包圍,所以可以用 group 方法獲取。
如果正則表達式不加 active(也就是匹配不帶 class 為 active 的節點內容),那會怎樣呢?我們將正則表達式中的 active 去掉,代碼改寫如下:
import re html = ''' <div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div> ''' result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S) if result:print(result.group(1), result.group(2))由于 search 方法會返回第一個符合條件的匹配目標,這里結果就變了:
把 active 標簽去掉后,從字符串開頭開始搜索,此時符合條件的節點就變成了第二個 li 節點,后面的就不再匹配,所以運行結果就變成第二個 li 節點中的內容了。
由于絕大部分的 HTML 文本都包含了換行符,所以盡量都需要加上 re.S 修飾符,以免出現匹配不到的問題。
findall()
前面我們介紹了 search 方法的用法,它可以返回匹配正則表達式的第一個內容,但是如果想要獲取匹配正則表達式的所有內容,這時就要借助 findall 方法了。該方法會搜索整個字符串,然后返回匹配正則表達式的所有內容。
import re html = ''' <div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div> ''' results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S) print(results) print(type(results)) for result in results:print(result)print(result[0], result[1], result[2])可以看到,返回的列表中的每個元素都是元組類型,我們用對應的索引依次取出即可。如果只是獲取第一個內容,可以用 search 方法。當需要提取多個內容時,可以用 findall 方法。
sub()
想要把一串文本中的所有數字都去掉,如果只用字符串的 replace 方法,那就太煩瑣了,這時可以借助 sub 方法。示例如下:
import recontent = '54aK54yr5oiR54ix5L2g' content = re.sub('\d+', '', content) print(content)這里只需要給第一個參數傳入 \d+ 來匹配所有的數字,第二個參數為替換成的字符串(如果去掉該參數的話,可以賦值為空),第三個參數是原字符串。
在上面的 HTML 文本中,如果想獲取所有 li 節點的歌名,直接用正則表達式來提取可能比較煩瑣。比如,可以寫成這樣子:
import re html = ''' <div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div> ''' results = re.findall('<li.*?>\s*?(<a.*?>)?(\w+)(</a>)?\s*?</li>', html, re.S) for result in results:print(result[1])此時借助 sub 方法就比較簡單了。可以先用 sub 方法將 a 節點去掉,只留下文本,然后再利用 findall 提取就好了:
import re html = ''' <div id="songs-list"><h2 class="title">經典老歌</h2><p class="introduction">經典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任賢齊">滄海一聲笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齊秦">往事隨風</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li><li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li><li data-view="5"><a href="/6.mp3" singer="鄧麗君">但愿人長久</a></li></ul> </div> ''' html = re.sub('<a.*?>|</a>', '', html) print(html) results = re.findall('<li.*?>(.*?)</li>', html, re.S) for result in results:print(result.strip())可以看到,a 節點經過 sub 方法處理后就沒有了,然后再通過 findall 方法直接提取即可。可以看到,在適當的時候,借助 sub 方法可以起到事半功倍的效果。
compile()
這個方法可以將正則字符串編譯成正則表達式對象,以便在后面的匹配中復用。示例代碼如下:
import recontent1 = '2019-12-15 12:00' content2 = '2019-12-17 12:55' content3 = '2019-12-22 13:21' 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)另外,compile 還可以傳入修飾符,例如 re.S 等修飾符,這樣在 search、findall 等方法中就不需要額外傳了。所以,compile 方法可以說是給正則表達式做了一層封裝,以便我們更好地復用。
總結
以上是生活随笔為你收集整理的《Python 3网络爬虫开发实战 》崔庆才著 第三章笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 科技视界杂志科技视界杂志社科技视界编辑部
- 下一篇: IP MAC捆绑原理及缺陷的讨论