python爬虫详解
python爬蟲詳解
1、基本概念
1.1、什么是爬蟲
? ? 網絡爬蟲,是一種按照一定規則,自動抓取互聯網信息的程序或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲。隨著網絡的迅速發展,萬維網成為大量信息的載體,如何有效地提取并利用這些信息成為一個巨大的挑戰。例如:傳統的通用搜索引擎AltaVista,Yahoo!和Google等,作為一個輔助人們檢索信息的工具也存在著一定的局限性,通用搜索引擎的目標是盡可能大的網絡覆蓋率,返回的結果包含大量用戶不關心的網頁,為了解決上述問題,定向抓取相關網頁資源的爬蟲應運而生。
? ? 由于互聯網數據的多樣性和資源的有限性,根據用戶需求定向抓取網頁并分析,已成為主流的爬取策略。只要你能通過瀏覽器訪問的數據都可以通過爬蟲獲取,爬蟲的本質是模擬瀏覽器打開網頁,獲取網頁中我們想要的那部分數據。
1.2、Python為什么適合爬蟲
? ? 因為python的腳本特性,python易于配置,對字符的處理也非常靈活,加上python有豐富的網絡抓取模塊,所以兩者經常聯系在一起。
? ??相比與其他靜態編程語言,如java,c#,C++,python抓取網頁文檔的接口更簡潔;相比其他動態腳本語言,如perl,shell,python的urllib2包提供了較為完整的訪問網頁文檔的API。此外,抓取網頁有時候需要模擬瀏覽器的行為,很多網站對于生硬的爬蟲抓取都是封殺的。這是我們需要模擬user agent的行為構造合適的請求,譬如模擬用戶登陸、模擬session/cookie的存儲和設置。在python里都有非常優秀的第三方包幫你搞定,如Requests,mechanize。
? ??抓取的網頁通常需要處理,比如過濾html標簽,提取文本等。python的beautifulsoap提供了簡潔的文檔處理功能,能用極短的代碼完成大部分文檔的處理。
1.3、Python爬蟲組成部分
? ? Python爬蟲架構主要由五個部分組成,分別是調度器、URL管理器、網頁下載器、網頁解析器、應用程序(爬取的有價值數據)。
? ? 調度器:相當于一臺電腦的CPU,主要負責調度URL管理器、下載器、解析器之間的協調工作。
? ? URL管理器:包括待爬取的URL地址和已爬取的URL地址,防止重復抓取URL和循環抓取URL,實現URL管理器主要用三種方式,通過內存、數據庫、緩存數據庫來實現。
? ? 網頁下載器:通過傳入一個URL地址來下載網頁,將網頁轉換成一個字符串,網頁下載器有urllib(Python官方內置標準庫)包括需要登錄、代理、和cookie,requests(第三方包)
? ? 網頁解析器:將一個網頁字符串進行解析,可以按照我們的要求來提取出我們有用的信息,也可以根據DOM樹的解析方式來解析。網頁解析器有正則表達式(直觀,將網頁轉成字符串通過模糊匹配的方式來提取有價值的信息,當文檔比較復雜的時候,該方法提取數據的時候就會非常的困難)、html.parser(Python自帶的)、beautifulsoup(第三方插件,可以使用Python自帶的html.parser進行解析,也可以使用lxml進行解析,相對于其他幾種來說要強大一些)、lxml(第三方插件,可以解析 xml 和 HTML),html.parser 和 beautifulsoup 以及 lxml 都是以 DOM 樹的方式進行解析的。
? ? 應用程序:就是從網頁中提取的有用數據組成的一個應用。
1.4、URI和URL的概念
? ? 在了解爬蟲前,我們還需要了解一下什么是URL?
1.4.1、網頁、網站、網絡服務器、搜素引擎
? ? 網頁:一份網頁文檔是交給瀏覽器顯示的簡單文檔。這種文檔是由超文本標記語言HTML來編寫的,網頁文檔可以插入各種各樣不同類型的資源:
- 樣式信息?— 控制頁面的觀感
- 腳本— 為頁面添加交互性
- 多媒體— 圖像,音頻,和視頻
? ? 網絡上所有可用的網頁都可以通過一個獨一無二的地址訪問到。要訪問一個頁面,只需在你的瀏覽器地址欄中鍵入頁面的地址,即URL。
? ??網站:網站是共享唯一域名的相互鏈接的網頁的集合。給定網站的每個網頁都提供了明確的鏈接—一般都是可點擊文本的形式—允許用戶從一個網頁跳轉到另一個網頁。要訪問網站,請在瀏覽器地址欄中輸入域名,瀏覽器將顯示網站的主要網頁或主頁。
? ??網絡服務器:一個網絡服務器是一臺托管一個或多個網站的計算機。 "托管"意思是所有的網頁和它們的支持文件在那臺計算機上都可用。網絡服務器會根據每位用戶的請求,將任意網頁從托管的網站中發送到任意用戶的瀏覽器中。別把網站和網絡服務器弄混了。例如,當你聽到某人說:"我的網站沒有響應",這實際上指的是網絡服務器沒響應,并因此導致網站不可用。
? ??搜索引擎:搜索引擎是一個特定類型的網站,用以幫助用戶在其他網站中尋找網頁。例如:有Google,?Bing,?Yandex,?DuckDuckGo等等。瀏覽器是一個接收并顯示網頁的軟件,搜索引擎則是一個幫助用戶從其他網站中尋找網頁的網站。
1.4.2、什么是URL
? ? 早在1989年,網絡發明人蒂姆·伯納斯 - 李(Tim Berners-Lee)就提出了網站的三大支柱:
? ? 1)URL?,跟蹤Web文檔的地址系統
? ? 2)HTTP,一個傳輸協議,以便在給定URL時查找文檔
? ? 3)HTML, 允許嵌入超鏈接的文檔格式
? ???Web的最初目的是提供一種簡單的方式來訪問,閱讀和瀏覽文本文檔。從那時起,網絡已經發展到提供圖像,視頻和二進制數據的訪問,但是這些改進幾乎沒有改變三大支柱。
? ??在Web之前,很難訪問文檔并從一個文檔跳轉到另一個文檔。WWW(World Wide Web,萬維網)簡稱為3W,使用統一資源定位符(URL)來標志WWW上的各種文檔。
? ??完整的工作流程如下∶
? ??1)Web用戶使用瀏覽器(指定URL)與Web服務器建立連接,并發送瀏覽請求。
? ? 2)Web服務器把URL轉換為文件路徑,并返回信息給 Web瀏覽器。
? ? 3)通信完成,關閉連接。
? ? HTTP:超文本傳送協議(HTTP)是在客戶程序(如瀏覽器)與WWW服務器程序之間進行交互所使用的協議。HTTP使用統一資源標識符(Uniform Resource Identifiers, URI)來傳輸數據和建立連接,它使用TCP連接進行可靠傳輸,服務器默認監聽在80端口。?
? ? URL:代表統一資源定位器。URL 只不過是 Web 上給定的唯一資源的地址。理論上,每個有效的 URL 都指向一個唯一的資源。此類資源可以是 HTML 頁面、CSS 文檔、圖像等。
? ? URL的組成:
? ? 1)協議部分(http:):它表示瀏覽器必須使用的協議來請求資源(協議是在計算機網絡中交換或傳輸數據的一套方法),通常對于網站,協議是 HTTPS 或 HTTP(其不安全版本)。這里使用的是HTTP協議,在"HTTP"后面的“//”為分隔符;
? ? 2)域名部分(www.example.com):一個URL中,也可以直接使用IP地址;
? ? 3)端口部分(80):域名和端口之間使用“:”作為分隔符。端口不是一個URL必須的部分,如果省略端口部分,將采用默認端口(默認端口可以省略)。
? ? 4)資源路徑:資源路徑包含,虛擬目錄部分和文件名部分
? ? 虛擬目錄部分(/path/to/):從域名后的第一個“/”開始到最后一個“/”為止,是虛擬目錄部分。虛擬目錄也不是一個URL必須的部分。
? ? 文件名部分(myfile.html):從域名后的最后一個“/”開始到“?”為止,是文件名部分,如果沒有“?”,則是從域名后的最后一個“/”開始到“#”為止,是文件部分。
? ? 6)參數部分(key1=value1&key2=value2):從“?”開始到“#”為止之間的部分為參數部分,又稱搜索部分、查詢部分。
? ? 7)錨部分(SomewhereInTheDocument):從“#”開始到最后,都是錨部分。錨點代表資源內的一種“書簽”,為瀏覽器提供顯示位于該“書簽”位置的內容的方向。例如,在 HTML 文檔中,瀏覽器將滾動到定義錨點的位置;在視頻或音頻文檔上,瀏覽器將嘗試轉到錨點所代表的時間。
? ??URI,是uniform resource identifier,統一資源標識符,用來唯一的標識一個資源。URL是uniform resource locator,統一資源定位器,它是一種具體的URI,即URL可以用來標識一個資源,而且還指明了如何locate這個資源。
1.5、引入模塊
? ? ?在進行爬蟲時,我們會用到一些模塊,怎么去使用這些模塊呢?
? ? 模塊(module):就是用來從邏輯上組織Python代碼(變量、函數、類),本質就是py文件,提供代碼的可維護性,Python使用import來導入模塊,如果沒有基礎的可以先看這篇文章:https://blog.csdn.net/xiaoxianer321/article/details/116723566。
? ? 導入模塊:
#導入內置模塊 import sys #導入標準庫 import os #導入第三方庫(需要安裝:pip install bs4) import bs4 from bs4 import BeautifulSoupprint(os.getcwd()) #打印當前工作目錄 #import bs4 導入整個模塊 print(bs4.BeautifulSoup.getText) #from bs4 import BeautifulSoup 導入指定模塊的部分屬性至當前工作空間 print(BeautifulSoup.getText)安裝方式1:在終端中使用命令
安裝方式二:pycharm在設置中安裝
? ? 我們大概會用到以下這些模塊:
import urllib.request,urllib.error #定制URL,獲取網頁數據 from bs4 import BeautifulSoup #網頁解析,獲取數據 import re #正則表達式,進行文件匹配 import xlwt #進行excel操作 import sqlite3 #進行SQLite數據庫操作2、urllib庫詳解
? ? Python3 中將 Python2 中的 urllib 和 urllib2 兩個庫整合為一個 urllib 庫,所以現在一般說的都是 Python3 中的 urllib 庫,它是python3內置標準庫,不需要額外安裝。
? ??urllib的四個模塊:
2.1、request模塊
? ??request模塊提供了最基本的構造 HTTP 請求的方法,利用它可以模擬瀏覽器的一個請求發起過程,同時它還帶有處理authenticaton(授權驗證),redirections(重定向),cookies(瀏覽器Cookies)以及其它內容。
2.1.1、urllib.request.urlopen() 函數
? ??打開一個url方法,返回一個文件對象HttpResponse。urlopen默認會發送get請求,當傳入data參數時,則會發起POST請求。
語法: urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)參數說明: url:請求的 url,也可以是request對象 data:請求的 data,如果設置了這個值,那么將變成 post 請求,如果要傳遞一個字典,則應該用urllib.parse模塊的urlencode()函數編碼; timeout:設置網站的訪問超時時間句柄對象; cafile和capath:用于 HTTPS 請求中,設置 CA 證書及其路徑; cadefault:忽略*cadefault*參數; context:如果指定了*context*,則它必須是一個ssl.SSLContext實例。urlopen() 返回對象HTTPResponse提供的方法和屬性:1)read()、readline()、readlines()、fileno()、close():對 HTTPResponse 類型數據進行操作; 2)info():返回 HTTPMessage 對象,表示遠程服務器 返回的頭信息 ; 3)getcode():返回 HTTP 狀態碼 geturl():返回請求的 url; 4)getheaders():響應的頭部信息; 5)status:返回狀態碼; 6)reason:返回狀態的詳細信息.案例一:使用urlopen()函數抓取百度
import urllib.request url = "http://www.baidu.com/" res = urllib.request.urlopen(url) # get方式請求 print(res) # 返回HTTPResponse對象<http.client.HTTPResponse object at 0x00000000026D3D00> # 讀取響應體 bys = res.read() # 調用read()方法得到的是bytes對象。 print(bys) # <!DOCTYPE html><!--STATUS OK-->\n\n\n <html><head><meta... print(bys.decode("utf-8")) # 獲取字符串內容,需要指定解碼方式,這部分我們放到html文件中就是百度的主頁# 獲取HTTP協議版本號(10 是 HTTP/1.0, 11 是 HTTP/1.1) print(res.version) # 11# 獲取響應碼 print(res.getcode()) # 200 print(res.status) # 200# 獲取響應描述字符串 print(res.reason) # OK# 獲取實際請求的頁面url(防止重定向用) print(res.geturl()) # http://www.baidu.com/# 獲取響應頭信息,返回字符串 print(res.info()) # Bdpagetype: 1 Bdqid: 0x803fb2b9000fdebb... # 獲取響應頭信息,返回二元元組列表 print(res.getheaders()) # [('Bdpagetype', '1'), ('Bdqid', '0x803fb2b9000fdebb'),...] print(res.getheaders()[0]) # ('Bdpagetype', '1') # 獲取特定響應頭信息 print(res.getheader(name="Content-Type")) # text/html;charset=utf-8? ? 在簡單的了解了一下使用urllib.request.urlopen(url)函數,會返回一個HTTPResponse對象,對象中包含了請求后響應的各項信息。
? ? 請求url最常見的方式莫過于發送get請求或post請求,為了更方便的看到效果,我們可以使用這個網站http://httpbin.org/來測試我們的請求。
案例二:get請求
我們在http://httpbin.org/網站,發送一個get測試請求:
然后我們在使用python模擬瀏覽器發送一個get請求
import urllib.request # 請求的URL url = "http://httpbin.org/get" # 模擬瀏覽器打開網頁(get請求) res = urllib.request.urlopen(url) print(res.read().decode("utf-8"))?請求結果如下:
我們會發現python模擬瀏覽器的請求很像。
?案例三:pos請求
import urllib.request import urllib.parseurl = "http://httpbin.org/post" # 按POST請求的格式封裝數據,請求內容,需要傳遞data data = bytes(urllib.parse.urlencode({"hello": "world"}), encoding="utf-8") res = urllib.request.urlopen(url, data=data) # 輸出響應結果 print(res.read().decode("utf-8"))模擬瀏覽器發出的請求(提交的數據會以form表單的形式發送出去),響應結果如下:
{"args": {}, "data": "", "files": {}, "form": {"hello": "world"}, "headers": {"Accept-Encoding": "identity", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Python-urllib/3.8", "X-Amzn-Trace-Id": "Root=1-60e0754e-7ea455cc757714f14db8f2d2"}, "json": null, "origin": "183.216.63.84", "url": "http://httpbin.org/post" }案例四:?偽裝Headers
? ? 通過上面的案例,不難發現使用urllib發送的請求,比較不同的地方是:"User-Agent",使用urllib發送的會有一個默認的Headers:User-Agent: Python-urllib/3.8。所以遇到一些驗證User-Agent的網站時,有可能會直接拒絕爬蟲,因此我們需要自定義Headers把自己偽裝的像一個瀏覽器一樣。
? ? 其實我們使用抓包工具也能看到http請求,使用抓包工具,抓取未指定請求頭的get請求如下:
? ? 而我直接使用谷歌瀏覽器時,使用抓包工具獲取到的User-Agent如下:
? ? 當然也可以直接在瀏覽器中查看:
?例如:我去爬取豆瓣網時:
import urllib.requesturl = "http://douban.com" resp = urllib.request.urlopen(url) print(resp.read().decode('utf-8'))返回錯誤:反爬蟲 raise HTTPError(req.full_url, code, msg, hdrs, fp) urllib.error.HTTPError: HTTP Error 418: HTTP 418 I'm a teapot客戶端錯誤響應代碼表示服務器拒絕煮咖啡,因為它是一個茶壺。這個錯誤是對1998年愚人節玩笑的超文本咖啡壺控制協議的引用。? ? 自定義Headers:
import urllib.requesturl = "http://douban.com" # 自定義headers headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36' } req = urllib.request.Request(url, headers=headers) # urlopen(也可以是request對象) print(urllib.request.urlopen(req).read().decode('utf-8')) # 獲取字符串內容,需要指定解碼方式? ? 當我再次使用抓包工具,抓取指定請求頭的get請求,結果如下:
案例五:設置請求超時時間
? ? ?我們在爬取網頁時,難免會遇到請求超時,或者無法響應的網址,為了提高代碼的健壯性,我可以設置請求超時時間。
import urllib.request,urllib.errorurl = "http://httpbin.org/get" try:resp = urllib.request.urlopen(url, timeout=0.01)print(resp.read().decode('utf-8')) except urllib.error.URLError as e:print("time out")輸出:time out2.1.2、urllib.request.urlretrieve()?函數
? ??urlretrieve()函數的作用是直接將遠程的數據下載到本地
# 語法:urlretrieve(url, filename=None, reporthook=None, data=None)# 參數說明url:傳入的網址 filename:指定了保存本地路徑(如果參數未指定,urllib會生成一個臨時文件保存數據) reporthook:是一個回調函數,當連接上服務器、以及相應的數據塊傳輸完畢時會觸發該回調,我們可以利用這個回調函數來顯示當前的下載進度 data:指 post 到服務器的數據,該方法返回一個包含兩個元素的(filename, headers)元組,filename 表示保存到本地的路徑,header表示服務器的響應頭使用案例:
import urllib.requesturl = "http://www.hao6v.com/" filename = "C:\\Users\\Administrator\\Desktop\\python_3.8.5\\電影.html" def callback(blocknum,blocksize,totalsize):"""@blocknum:目前為此傳遞的數據塊數量@blocksize:每個數據塊的大小,單位是byte,字節@totalsize:遠程文件的大小"""if totalsize == 0:percent = 0else:percent = blocknum * blocksize / totalsizeif percent > 1.0:percent = 1.0percent = percent * 100print("download : %.2f%%" % (percent))local_filename, headers= urllib.request.urlretrieve(url, filename, callback)案例效果:
2.2、error模塊
? ??urllib.error 模塊為 urllib.request 所引發的異常定義了異常類,基礎異常類是 URLError。
2.2.1、HTTP協議(RFC2616)狀態碼定義
? ? 所有HTTP響應的第一行都是狀態行,依次是當前HTTP版本號,3位數字組成的狀態代碼,以及描述狀態的短語,彼此由空格分隔。?
? ? 狀態代碼的第一個數字代表當前響應的類型:
? ? 1xx消息——請求已被服務器接收,繼續處理
? ? 2xx成功——請求已成功被服務器接收、理解、并接受
? ? 3xx重定向——需要后續操作才能完成這一請求
? ? 4xx請求錯誤——4xx類的狀態碼用于看起來客戶端有錯誤的情況下,請求含有詞法錯誤或者無法被執行
? ? 5xx服務器錯誤——由數字“5”打頭的響應狀態碼表示服務器已經明顯處于錯誤的狀況下或沒有能力執行請求,或在處理某個正確請求時發生錯誤。
部分狀態碼如下:
| 狀態碼 | 定義 |
| 100 | 繼續。客戶端應該繼續它的請求。該間歇響應用于提醒客戶端服務器已經接收和接受請求的開 始部分。 客戶端應該繼續發送請求的剩余部分, 或者如果請求已經發送完了, 就乎略該響應。 服務器在請求完成后必須發送最終響應。 |
| 101 | 切換協議。 |
| 200 | OK。請求已經成功。該響應返回的信息取決于請求中使用的方法,例如: GET與所請求資源相對應的實體將在響應中發送; HEAD 與所請求資源相對應的實體頭部將在響應中發送,而沒有消息體; POST描述或包含行為結果的實體; TRACE 包含終點服務器收到的請求消息的實體。 |
| 201 | 創建。請求全部成功,且創建了新資源。原始服務器必須在返回 201 狀態碼之前創建資源。 如果該行為不能立即實施,服務器應該代之以202(Accepted)響應。 |
| 202 | 請求已經接受處理,但是處理還沒有完成。 |
| 203 | 實體頭部中返回的元信息不是在原始服務器有效的確定集合, 而是從本地或第三方拷貝 收集的。現在的集合可能是原始版本的子集或超集。 |
| 204 | 服務器已經完成請求,但不需要返回實體,且可能希望返回更新的元信息。響應可能包 括新的或更新的元信息,通過實體頭部的形式。如果存在這些頭部,則應該與所請求變量相 關。 |
| 205 | 重置內容。服務器已經完成請求且用戶代理應該復位引起請求發送的文檔視圖。 |
| 300 | 多重選項。所請求的資源符合表述集合中的任何一個,每個都有它自己的特殊位置。代理驅動的協 商信息提供給用戶(或用戶代理)來選擇喜歡的表述,并重定向請求到它的位置。? |
| 301 | 所請求的資源已經指定到一個新的永久 URI, 且將來任何對該資源的引用都應該使用所 返回的 URI 之一。 |
| 302 | ?所請求的資源臨時存在于不同的 URI。 |
| 303 | 請求的響應可以在不同的URI中發現,且應該使用GET方法到該資源來獲取它。 |
| 307 | 臨時重定向 |
| 400 | ?服務器不能理解請求,由于畸形的語法。 |
| 403 | 服務器理解請求, 但拒絕完成它。 認證也沒用, 請求不該重復。 |
| 404 | 未找到。服務器不能發現匹配Request-URI的任何東西。 |
| 408 | 請求超時 |
| 500 | 服務器錯誤 |
| 503 | 服務不可用 |
| 504 | 網關超時 |
| 505 | HTTP版本不支持 |
2.2.2、?urllib.error.URLError
import urllib.request,urllib.errortry:url = "http://www.baidus.com"resp = urllib.request.urlopen(url)print(resp.read().decode('utf-8')) # except urllib.error.HTTPError as e: # print("請檢查url是否正確") # URLError是urllib.request異常的超類 except urllib.error.URLError as e:if hasattr(e, "code"):print(e.code)if hasattr(e, "reason"):print(e.reason)案例效果:
? ? ?URLError,為urllib.request 所引發的基礎異常類,這里打印出來的403,就是urllib.error.HTTPError,另外還有一個ContentTooShortError,此異常會在 urlretrieve() 函數檢測到已下載的數據量小于期待的數據量(由 Content-Length 頭給定)時被引發。?
2.3、parse 模塊
? ? urllib.parse 模塊提供了很多解析和組建 URL 的函數。下面只列出了部分
? ? 解析url的函數:urllib.parse.urlparse、urllib.parse.urlsplit、urllib.parse.urldefrag
? ? 組件url的函數:urllib.parse.urlunparse、urllib.parse.urljoin
????查詢參數的構造與解析:urllib.parse.urlencode、urllib.parse.parse_qs、
urllib.parse.parse_qsl2.3.1、urllib.parse.urlparse
# 語法 urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)scheme:設置默認值 allow_fragments:是否允許fragment? ? ?將URL解析成 ParseResult 對象。對象中包含了六個元素:也就是我們前面說過的url的組成,只不過urlparse函數,將其解析成6個元素。
| scheme | 0 | URL協議 | scheme?參數 |
| netloc | 1 | 網絡位置部分(域名) | 空字符串 |
| path | 2 | 分層路徑 | 空字符串 |
| params | 3 | 最后路徑元素的參數 | 空字符串 |
| query | 4 | 查詢參數 | 空字符串 |
| fragment | 5 | 片段識別 | 空字符串 |
?使用案例:
import urllib.parseurl = "http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2#SomewhereIntheDocument" parsed_result = urllib.parse.urlparse(url) print(parsed_result) print('協議-scheme :', parsed_result.scheme) print('域名-netloc :', parsed_result.netloc) print('路徑-path :', parsed_result.path) print('路徑參數-params :', parsed_result.params) print('查詢參數-query :', parsed_result.query) print('片段-fragment:', parsed_result.fragment) print('用戶名-username:', parsed_result.username) print('密碼-password:', parsed_result.password) print('主機名-hostname:', parsed_result.hostname) print('端口號-port :', parsed_result.port)輸出結果: ParseResult(scheme='http', netloc='www.example.com:80', path='/path/to/myfile.html', params='', query='key1=value&key2=value2', fragment='SomewhereIntheDocument') 協議-scheme : http 域名-netloc : www.example.com:80 路徑-path : /path/to/myfile.html 路徑參數-params : 查詢參數-query : key1=value&key2=value2 片段-fragment: SomewhereIntheDocument 用戶名-username: None 密碼-password: None 主機名-hostname: www.example.com 端口號-port : 802.3.2、urllib.parse.urlsplit
? ? 這類似于urlparse,所不同的是, urlsplit() 并不會把路徑參數(params) 從 路徑(path) 中分離出來。此函數返回一個名為tuple的5項:(協議、域名、路徑、查詢、片段標識符)
使用案例:
import urllib.parseurl = "http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2#SomewhereIntheDocument" # urlsplit分割,唯一的區別就是不會把params拆分出來 parsed_result = urllib.parse.urlsplit(url) print(parsed_result) print('協議-scheme :', parsed_result.scheme) print('域名-netloc :', parsed_result.netloc) print('路徑-path :', parsed_result.path) # parsed_result.params 沒有這項 print('查詢參數-query :', parsed_result.query) print('片段-fragment:', parsed_result.fragment) print('用戶名-username:', parsed_result.username) print('密碼-password:', parsed_result.password) print('主機名-hostname:', parsed_result.hostname) print('端口號-port :', parsed_result.port)2.3.3、urllib.parse.urlsplit?
? ? urllib.parse.urldefrag,如果url包含片段標識符,則返回修改后的url版本(不包含片段標識符),并將片段標識符作為單獨的字符串返回。如果url中沒有片段標識符,則返回原url和空字符串。
使用案例:
import urllib.parseurl = "http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2#SomewhereIntheDocument" parsed_result = urllib.parse.urldefrag(url) print(parsed_result) # DefragResult(url='http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2', fragment='SomewhereIntheDocument') url1 = "http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2" parsed_result1 = urllib.parse.urldefrag(url1) print(parsed_result1)# 輸出結果: # DefragResult(url='http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2', fragment='SomewhereIntheDocument') # DefragResult(url='http://www.example.com:80/path/to/myfile.html?key1=value&key2=value2', fragment='')2.3.4、urllib.parse.urlunparse
? ??urlunparse()接收一個列表的參數,而且列表的長度是有要求的,是必須六個參數以上,否則拋出異常。
import urllib.parseurl_compos = ('http', 'www.example.com:80', '/path/to/myfile.html', 'params2', 'query=key1=value&key2=value2', 'SomewhereIntheDocument') print(urllib.parse.urlunparse(url_compos))# 輸出結果: # http://www.example.com:80/path/to/myfile.html;params2?query=key1=value&key2=value2#SomewhereIntheDocument2.3.5、urllib.parse.urljoin
import urllib.parse# 連接兩個參數的url, 將第二個參數中缺的部分用第一個參數的補齊,如果第二個有完整的路徑,則以第二個為主。 print(urllib.parse.urljoin('https://movie.douban.com/', 'index')) print(urllib.parse.urljoin('https://movie.douban.com/', 'https://accounts.douban.com/login'))# 輸出結果: # https://movie.douban.com/index # https://accounts.douban.com/login2.3.6、urllib.parse.urlencode
? ? 可以將一個 dict 轉換成合法的查詢參數。
import urllib.parsequery_args = {'name': 'dark sun','country': '中國' } query_args = urllib.parse.urlencode(query_args) print(query_args)# 輸出結果 # name=dark+sun&country=%E4%B8%AD%E5%9B%BD2.3.7、urllib.parse.parse_qs
? ? 解析作為字符串參數提供的查詢字符串,數據作為字典返回。字典鍵是唯一的查詢變量名,值是每個名稱的值列表。
import urllib.parsequery_args = {'name': 'dark sun','country': '中國' } query_args = urllib.parse.urlencode(query_args) print(query_args) # name=dark+sun&country=%E4%B8%AD%E5%9B%BDprint(urllib.parse.parse_qs(query_args)) # 返回字典 print(urllib.parse.parse_qsl(query_args)) # 返回列表# 輸出結果 # {'name': ['dark sun'], 'country': ['中國']} # [('name', 'dark sun'), ('country', '中國')]2.4、robotparser模塊
? ? 此模塊提供了一個單獨的類?RobotFileParser,它可以回答關于某個特定用戶代理是否能在 Web 站點獲取發布?robots.txt?文件的 URL 的問題。(/robots.txt該文件是一個簡單的基于文本的訪問控制系統,文件向網絡機器人提供有關其網站的說明,什么機器人可以訪問,哪些鏈接不可以訪問)
3、BeautifulSoup4
? ??學了urllib標準庫之后,我們已經能爬到些比較正常的網頁源碼(html文檔)了,但這離結果還差一步——就是如何篩選我們想要的數據,這時候BeautifulSoup庫就來了,BeautifulSoup目前最新版本為BeautifulSoup4。
3.1、BeautifulSoup4的簡介及使用
3.1.1、BeautifulSoup4的簡介
? ? Beautiful Soup 官方定義:是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工作時間。(官網文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/)
? ??BeautifulSoup本身支持Python標準庫中的HTML解析器,但若想使BeautifulSoup使用html5lib/lxml 解析器解析器,可以使用下面方法。(官方推薦:使用lxml作為解析器,因為效率更高。)
pip install html5lib pip install lxml3.1.2、BeautifulSoup4的使用
? ? ?BeautifulSoup(markup, features)接受兩個參數:
? ? ?第一個參數(markup):文件對象或字符串對象
? ? ?第二個參數(features):解析器,未指定則使用python標準解析器(html.parser),但會產警告
from bs4 import BeautifulSoup # 導入BeautifulSoup4庫# 未指定就使用html.parser這個python標準解析器 BeautifulSoup(markup, "html.parser") 未指定會產生警告 GuessedAtParserWarning: No parser was explicitly specified# BeautifulSoup 第一個參數接受:一個文件對象或字符串對象 soup1 = BeautifulSoup(open("C:\\Users\\Administrator\\Desktop\\python_3.8.5\\電影.html")) soup2 = BeautifulSoup("<html>hello python</html>") # 得到文檔的對象 print(type(soup2)) # <class 'bs4.BeautifulSoup'> print(soup1) # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" .... print(soup2) # <html><head></head><body>hello python</body></html>3.2、對象的種類
? ? ?BeautifulSoup將復雜HTML文檔轉換成一個復雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種: Tag , NavigableString , BeautifulSoup , Comment 。
3.2.1、Tag 標簽對象
? ??Tag有很多方法和屬性,在?遍歷文檔樹?和?搜索文檔樹?中有詳細解釋.現在介紹一下tag中最重要的屬性: name和attribute。
3.2.2、NavigableString 對象(可以遍歷的字符串)
? ?被包含在一個標簽對中的字符串內容,可用tag.string來獲取其內容(標簽內容中含注釋或其他標簽均無法獲取)
3.2.3、BeautifulSoup 對象
? ??表示的是一個文檔(document)的全部內容
3.2.4、Comment?對象(注釋及特殊字符串)
? ??Comment 對象是一個特殊類型的 NavigableString 對象,其實輸出的內容仍然不包括注釋符號,但是如果不好好處理它,可能會對我們的文本處理造成意想不到的麻煩。
使用案例:
from bs4 import BeautifulSoup # 導入BeautifulSoup4庫 # python 標準解析器 未指定就使用這個 BeautifulSoup(markup, "html.parser") soup2 = BeautifulSoup("<html>""<p class='boldest'>我是p標簽<b>hello python</b></p>""<!--我是標簽外部的內容注釋-->""<p class='boldest2'><!--我p標簽內的注釋-->我是獨立的p標簽</p>""<a><!--我a標簽內的注釋-->我是鏈接</a>""<h1><!--這是一個h1標簽的注釋--></h1>""</html>","html5lib") # 得到文檔的對象 # Tag 標簽對象 print(type(soup2.p)) # 輸出Tag對象<class 'bs4.element.Tag'> print(soup2.p.name) # 輸出Tag標簽對象的名稱 print(soup2.p.attrs) # 輸出第一個p標簽的屬性信息:{'class': ['boldest']} soup2.p['class'] = ['boldest', 'boldest1'] print(soup2.p.attrs) # {'class': ['boldest', 'boldest1']}# NavigableString 可以遍歷的字符串對象 print(type(soup2.b.string)) # <class 'bs4.element.NavigableString'> print(soup2.b.string) # hello python print(soup2.a.string) # None 存在注釋或者其他標簽內容均無法獲取 print(soup2.b.string.replace_with("hello world")) # replace_with()方法可替換標簽中的內容 print(soup2.b.string) # hello world# BeautifulSoup 對象 print(type(soup2)) # <class 'bs4.BeautifulSoup'> print(soup2) # <html><head></head><body><p class="boldest">我是p標簽<b>hello python</b></p><!--我是標簽外部的內容注釋--><p><!--我p標簽內的注釋-->我是獨立的p標簽</p></body></html> print(soup2.name) # [document]# Comment 注釋及特殊字符串(是一個特殊類型的 NavigableString 對象) print(type(soup2.h1.string)) # <class 'bs4.element.Comment'> print(soup2.h1.string) # 這是一個h1標簽的注釋 (利用 .string 來輸出它的內容,注釋符被去除了,不是我們想要的) print(soup2.h1.prettify()) # 會以特殊格式輸出:<h1> <!--這是一個h1標簽的注釋--> </h1>3.3、對象屬性-遍歷文檔
3.3.1、子節點
| 屬性(BeautifulSoup對象) | 描述 |
| .tag標簽名 | 使用標簽名獲取一個標簽及其內容 |
| .contents / .chidren | 將tag的子節點以列表的方式輸出 |
| .descendants | 可以對所有tag的子孫節點進行遞歸循環 |
| .string | 如果tag只有一個 NavigableString 類型子節點,那么這個tag可以使用 .string 得到子節點 |
| .strings/stripped_strings | tag中有多個字符串,可以使用.strings來循環獲取stripped_strings可以去除多余空白內容 |
使用案例:
from bs4 import BeautifulSoupmarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>I’m the title</title></head><body><h1>HelloWorld</h1><div><div><p><b>我是一個段落...</b>我是第一段我是第二段<b>我是另一個段落</b>我是第一段</p><a>我是一個鏈接</a></div><div><p>picture</p><img src="example.png"/></div></div></body> </html>''' soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 print(soup.head.name) # soup.head可以獲取標簽,獲取標簽名 - 輸出:head print(soup.head.contents) # 將tag的子節點以列表的方式輸出--輸出:['\n ', <meta charset="utf-8"/>, '\n ', <title>I’m the title</title>, '\n '] print(soup.head.contents[1]) # <meta charset="utf-8"/> print(soup.head.children) # list_iterator object for child in soup.head.children:print(child) # <meta charset="utf-8"/> <title>I’m the title</title> # 標簽中的內容其實也是一個節點 使用contents和children無法直接獲取間接節點中的內容,但是.descendants 屬性可以 for child in soup.head.descendants:print(child) # <meta charset="utf-8"/> <title>I’m the title</title> I’m the title print(soup.head.title.string) # 輸出:I’m the title 注:title中有其他節點或者注釋都無法獲取print(soup.body.div.div.p.strings) # 使用.string-None 使用.strings 獲得generator object for string in soup.body.div.div.p.stripped_strings: # stripped_strings 可以去除多余空白內容print(repr(string)) # '我是一個段落...'# '我是第一段\n 我是第二段'# '我是另一個段落'# '我是第一段'3.3.2、父節點
| 屬性 | 描述 |
| .parent | 獲取某個元素的父節點 |
| .parents | 可以遞歸得到元素的所有父輩節點 |
使用案例:
from bs4 import BeautifulSoupmarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>I’m the title</title></head><body><h1>HelloWorld</h1><div><div><p><b>我是一個段落...</b>我是第一段我是第二段<b>我是另一個段落</b>我是第一段</p><a>我是一個鏈接</a></div><div><p>picture</p><img src="example.png"/></div></div></body> </html>''' soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 title = soup.head.title print(title.parent) # 輸出父節點 # <head> # <meta charset="utf-8"/> # <title>I’m the title</title> # </head> print(title.parents) # generator object PageElement.parents for parent in title.parents:print(parent) # 輸出head父節點 和 html父節點3.3.3、兄弟節點
| 屬性 | 描述 |
| .next_sibling | 查詢兄弟節點,表示下一個兄弟節點 |
| .previous_sibling | 查詢兄弟節點,表示上一個兄弟節點 |
| .next_siblings | 對當前節點的兄弟節點迭代輸出(下) |
| .previous_siblings | 對當前節點的兄弟節點迭代輸出(上) |
使用案例:
from bs4 import BeautifulSoupmarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>I’m the title</title></head><body><div><p><b id=“b1”>我是第一個段落</b><b id=“b2”>我是第二個段落</b><b id=“b3”>我是第三個段落</b><b id=“b4”>我是第四個段落</b></p><a>我是一個鏈接</a></div></body> </html>''' soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 p = soup.body.div.p.b print(p) # <b id="“b1”">我是第一個段落</b> print(p.next_sibling) # <b id="“b2”">我是第二個段落</b> print(p.next_sibling.previous_sibling) # <b id="“b1”">我是第一個段落</b> print(p.next_siblings) # generator object PageElement.next_siblings for nsl in p.next_siblings:print(nsl) # <b id="“b2”">我是第二個段落</b># <b id="“b3”">我是第三個段落</b># <b id="“b4”">我是第四個段落</b>3.3.4、回退和前進
| 屬性 | 描述 |
| .next_element | 解析下一個元素對象 |
| .previous_element | 解析上一個元素對象 |
| .next_elements | 迭代解析元素對象 |
| .previous_elements | 迭代解析元素對象 |
使用案例:
from bs4 import BeautifulSoupmarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>I’m the title</title></head><body><div><p><b id=“b1”>我是第一個段落</b><b id=“b2”>我是第二個段落</b><b id=“b3”>我是第三個段落</b><b id=“b4”>我是第四個段落</b></p><a>我是一個鏈接<h3>h3</h3></a></div></body> </html>''' soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 p = soup.body.div.p.b print(p) # <b id="“b1”">我是第一個段落</b> print(p.next_element) # 我是第一個段落 print(p.next_element.next_element) # <b id="“b2”">我是第二個段落</b> print(p.next_element.next_element.next_element) # 我是第二個段落 for element in soup.body.div.a.next_element: # 對:我是一個鏈接 字符串的遍歷print(element)? ? 注:next_element,會把標簽中的內容,也會認為是一個節點。例如:案例中取a節點的next_element,則是一個字符串(我是一個鏈接)
3.4、對象的屬性和方法-搜索文檔樹
? ? 這里的搜索文檔,其實就是按照某種條件去搜索過濾文檔,過濾的規則,往往會使用搜索的API,或者我們也可以自定義正則/過濾器,去搜索文檔。
3.4.1、find_all()
? ??最簡單的過濾器是字符串.在搜索方法中傳入一個字符串參數,Beautiful Soup會查找與字符串完整匹配的內容。
語法:find_all(?name?,?attrs?,?recursive?,?string?,?**kwargs?)? 返回列表list
find_all(?name?,?attrs?,?recursive?,?string?,?**kwargs?)?參數說明: name:查找所有名字為 name 的tag(name可以是字符串,也可以是列表) attrs: 對標簽屬性值的檢索字符串,可標注屬性檢索 recursive: 是否對子孫全部檢索,默認True string: <>…</>中字符串區域的檢索字符串使用案例:
from bs4 import BeautifulSoup import remarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title id="myTitle">I’m the title</title></head><body><div><p><b id=“b1” class="bcl1">我是第一個段落</b><b>我是第二個段落</b><b id=“b3”>我是第三個段落</b><b id=“b4”>我是第四個段落</b></p><a href="www.temp.com">我是一個鏈接<h3>h3</h3></a><div id="dv1">str</div></div></body> </html>''' # 語法:find_all( name , attrs , recursive , string , **kwargs ) soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 # 第一個參數name,可以是一個標簽名也可以是列表 print(soup.findAll('b')) # 返回包含b標簽的列表 [<b id="“b1”">我是第一個段落</b>, <b id="“b2”">我是第二個段落</b>, <b id="“b3”">我是第三個段落</b>, <b id="“b4”">我是第四個段落</b>] print(soup.findAll(['a', 'h3'])) # 按列表匹配多個 [<a href="www.temp.com">我是一個鏈接<h3>h3</h3></a>, <h3>h3</h3>]# 第二個參數attrs,可以指定參數名字,也可以不指定 print(soup.findAll('b', 'bcl1')) # 匹配class='bcl1'的b標簽[<b class="bcl1" id="“b1”">我是第一個段落</b>] print(soup.findAll(id="myTitle")) # 指定id [<title id="myTitle">I’m the title</title>] print(soup.find_all("b", attrs={"class": "bcl1"})) # [<b class="bcl1" id="“b1”">我是第一個段落</b>] print(soup.findAll(id=True)) # 匹配所有有id屬性的標簽# 第三個參數recursive 默認True 如果只想搜索tag的直接子節點,可以使用參數 recursive=False print(soup.html.find_all("title", recursive=False)) # [] recursive=False。找html的直接子節點,是head,所以找不到title# 第四個參數string print(soup.findAll('div', string='str')) # [<div id="dv1">str</div>] print(soup.find(string=re.compile("我是第二個"))) # 搜索我是第二個段落# 其他參數 limit 參數 print(soup.findAll('b', limit=2)) # 當搜索到的結果數量達到 limit 的限制時,就停止搜索返回結果,[<b class="bcl1" id="“b1”">我是第一個段落</b>, <b>我是第二個段落</b>]?3.4.2、find()?
? ??find()與find_all()?的區別是?find_all()?方法的返回結果是值包含一個元素的列表,而?find()?方法直接返回結果(即找到了就不再找,只返第一個匹配的),find_all()?方法沒有找到目標是返回空列表,?find()?方法找不到目標時,返回?None。
? ? 語法:find(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
使用案例:
soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 # 第一個參數name,可以是一個標簽名也可以是列表 print(soup.find('b')) # 返回<b class="bcl1" id="“b1”">我是第一個段落</b>,只要找到一個即返回# 第二個參數attrs,可以指定參數名字,也可以不指定 print(soup.find('b', 'bcl1')) # <b class="bcl1" id="“b1”">我是第一個段落</b> print(soup.find(id="myTitle")) # <title id="myTitle">I’m the title</title> print(soup.find("b", attrs={"class": "bcl1"})) # <b class="bcl1" id="“b1”">我是第一個段落</b> print(soup.find(id=True)) # 匹配到第一個<title id="myTitle">I’m the title</title># 第三個參數recursive 默認True 如果只想搜索tag的直接子節點,可以使用參數 recursive=False print(soup.html.find("title", recursive=False)) # None recursive=False。找html的直接子節點,是head,所以找不到title# 第四個參數string print(soup.find('div', string='str')) # [<div id="dv1">str</div>] print(soup.find(string=re.compile("我是第二個"))) # 我是第二個段落3.4.3、find_parents() 和 find_parent()
? ??find_parents()?和?find_parent()?用來搜索當前節點的父輩節點。
? ? 語法:
? ? find_parents(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? find_parent(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
3.4.4、find_next_siblings() 和 find_next_sibling()
? ??find_next_siblings()?方法返回所有符合條件的后面的兄弟節點,find_next_sibling()?只返回符合條件的后面的第一個tag節點;
? ? 語法:
? ??find_next_siblings(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? find_next_sibling(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
3.4.5、find_previous_siblings() 和 find_previous_sibling()
? ??通過?.previous_siblings?屬性對當前tag的前面解析。find_previous_siblings()?方法返回所有符合條件的前面的兄弟節點,find_previous_sibling()?方法返回第一個符合條件的前面的兄弟節點;
? ? 語法:
? ? find_previous_siblings(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? find_previous_sibling(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
3.4.6、find_all_next() 和 find_next()
? ??find_all_next()?方法返回所有符合條件的節點,find_next()?方法返回第一個符合條件的節點。
? ? ?語法:
? ??find_all_next(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? find_next(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
3.4.7、find_all_previous() 和 find_previous()
? ??find_all_previous()?方法返回所有符合條件的節點元素,find_previous()?方法返回第一個符合條件的節點元素。
? ? 語法:
? ? find_all_previous(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? find_previous(?name?,?attrs?,?recursive?,?string?,?**kwargs?)
? ? 這些其實和前面的屬性用法類似,但是比屬性又多了像find_all()一樣的參數。這里就不再詳細介紹了,可以看官網的API。
3.4.8、CSS選擇器查找
? ??Beautiful Soup支持大部分的CSS選擇器,在?Tag?或?BeautifulSoup?對象的?.select()?方法中傳入字符串參數, 即可使用CSS選擇器的語法找到tag。
使用案例:
from bs4 import BeautifulSoup import remarkup = '''<!DOCTYPE html> <html><head><meta charset="UTF-8"><title id="myTitle">I’m the title</title></head><body><div><p><b id=“b1” class="bcl1">我是第一個段落</b><b>我是第二個段落</b><b id=“b3”>我是第三個段落</b><b id=“b4”>我是第四個段落</b></p><a href="www.temp.com">我是一個鏈接<h3>h3</h3></a><div id="dv1">str</div></div></body> </html>''' soup = BeautifulSoup(markup, "html5lib") # BeautifulSoup 對象 print(soup.select("html head title")) # [<title id="myTitle">I’m the title</title>] print(soup.select("body a")) # [<a href="www.temp.com">我是一個鏈接<h3>h3</h3></a>] print(soup.select("#dv1")) # [<div id="dv1">str</div>]3.5、對象的屬性和方法-修改文檔樹
3.5.1、修改tag的名稱和屬性
使用案例:
from bs4 import BeautifulSoupsoup = BeautifulSoup('<b class="boldest">Extremely bold</b>', "html5lib") tag = soup.b tag.name = "blockquote" print(tag) # <blockquote class="boldest">Extremely bold</blockquote> tag['class'] = 'veryBold' tag['id'] = 1 print(tag) # <blockquote class="veryBold" id="1">Extremely bold</blockquote>del tag['id'] # 刪除屬性3.5.2、修改 .string
? ??tag的?.string?屬性賦值,就相當于用當前的標簽中的內容
from bs4 import BeautifulSoupsoup = BeautifulSoup('<b class="boldest">Extremely bold</b>', "html5lib") tag = soup.b tag.string = "replace" print(tag) # <b class="boldest">replace</b>3.5.3、append()
? ? ?向tag中添加內容
from bs4 import BeautifulSoupsoup = BeautifulSoup('<b class="boldest">Extremely bold</b>', "html5lib") tag = soup.b tag.append(" append") print(tag) # <b class="boldest">Extremely bold append</b>3.5.4、NavigableString() 和 .new_tag()
from bs4 import BeautifulSoup, NavigableString, Commentsoup = BeautifulSoup('<div><b class="boldest">Extremely bold</b></div>', "html5lib") tag = soup.div new_string = NavigableString('NavigableString') tag.append(new_string) print(tag) # <div><b class="boldest">Extremely bold</b>NavigableString</div>new_comment = soup.new_string("Nice to see you.", Comment) tag.append(new_comment) print(tag) # <div><b class="boldest">Extremely bold</b>NavigableString<!--Nice to see you.--></div># 添加標簽,推薦使用工廠方法new_tag new_tag = soup.new_tag("a", href="http://www.example.com") tag.append(new_tag) print(tag) # <div><b class="boldest">Extremely bold</b>NavigableString<!--Nice to see you.--><a href="http://www.example.com"></a></div>3.5.5、insert()
? ??把元素插入到指定的位置
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup,"html5lib") tag = soup.a tag.insert(1, "but did not endorse ") # 和append的區別就是.contents屬性獲取不一致 print(tag) # <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a> print(tag.contents) # ['I linked to ', 'but did not endorse ', <i>example.com</i>]3.5.6、insert_before() 和 insert_after()
? ??當前tag或文本節點前/后插入內容
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to</a>' soup = BeautifulSoup(markup, "html5lib") tag = soup.new_tag("i") tag.string = "Don't" soup.a.string.insert_before(tag) print(soup.a) # <a href="http://example.com/"><i>Don't</i>I linked to</a>soup.a.i.insert_after(soup.new_string(" ever ")) print(soup.a) # <a href="http://example.com/"><i>Don't</i> ever I linked to</a>3.5.7、clear()
? ??移除當前tag的內容
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to</a>' soup = BeautifulSoup(markup, "html5lib") tag = soup.a tag.clear() print(tag) # <a href="http://example.com/"></a>3.5.8、extract()
? ??將當前tag移除文檔樹,并作為方法結果返回
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, "html5lib") a_tag = soup.a i_tag = soup.i.extract()print(a_tag) # <a href="http://example.com/">I linked to </a> print(i_tag) # <i>example.com</i> 我們移除的內容3.5.9、decompose()
? ??將當前節點移除文檔樹并完全銷毀
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, "html5lib") a_tag = soup.a soup.i.decompose() print(a_tag) # <a href="http://example.com/">I linked to </a>3.5.10、replace_with()
? ??移除文檔樹中的某段內容,并用新tag或文本節點替代它
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, "html5lib") new_tag = soup.new_tag("b") new_tag.string = "example.net" soup.a.i.replace_with(new_tag) print(soup.a) # <a href="http://example.com/">I linked to <b>example.net</b></a>3.5.11、wrap()和unwrap()
? ??wrap()對指定的tag元素進行包裝,unwrap()移除tag內的所有tag標簽,該方法常被用來進行標記的解包
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, "html5lib") a_tag = soup.aa_tag.i.unwrap() print(a_tag) # <a href="http://example.com/">I linked to example.com</a>soup2 = BeautifulSoup("<p>I wish I was bold.</p>", "html5lib") soup2.p.string.wrap(soup2.new_tag("b")) print(soup2.p) # <p><b>I wish I was bold.</b></p>3.6、輸出
3.6.1、格式化輸出
? ? prettify()?方法將Beautiful Soup的文檔樹格式化后以Unicode編碼輸出,每個XML/HTML標簽都獨占一行。
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup, "html5lib") print(soup) # <html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html> print(soup.prettify()) #<html># <head># </head># <body># <a href="http://example.com/"># I linked to# <i># example.com# </i># </a># </body># </html>3.6.2、壓縮輸出
? ??如果只想得到結果字符串,不重視格式,那么可以對一個?BeautifulSoup?對象或?Tag?對象使用Python的str()?方法。
3.6.3、get_text()只輸出tag中的文本內容
? ??如果只想得到tag中包含的文本內容,那么可以調用?get_text()?方法。
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">I linked to <i>example.com</i>點我</a>' soup = BeautifulSoup(markup, "html5lib") print(soup) # <html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html> print(str(soup)) # <html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i>點我</a></body></html> print(soup.get_text()) # I linked to example.com4、re標準庫(模塊)
? ? BeautifulSoup庫,重html文檔中篩選我們想要的數據,但這些數據可能還有很多更細致的內容,比如,我們取到的是不是我們想要的鏈接、是不是我們需要提取的郵箱數據等等,為了更細致精確的提取數據,那么正則來了。
? ? 正則表達式(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE),是計算機科學的一個概念。正則表達式使用單個字符串來描述、匹配一系列匹配某個句法規則的字符串。在其他語言中,我們也經常會接觸到正則表達式。? ? ? ? ? ? ??
?使用案例:
import re# 創建正則對象 pat = re.compile('\d{2}') #出現2次數字的 # search 在任意位置對給定的正則表達式模式搜索第一次出現的匹配情況 s = pat.search("12abc") print(s.group()) # 12# match 從字符串起始部分對模式進行匹配 m = pat.match('1224abc') print(m.group()) # 12# search 和 match 的區別 匹配的位置不也一樣 s1 = re.search('foo', 'bfoo').group() print(s1) # foo try:m1 = re.match('foo','bfoo').group() # AttributeError except:print('匹配失敗') # 匹配失敗# 原生字符串(\B 不是以py字母結尾的) allList = ["py!", "py.", "python"] for li in allList:# re.match(正則表達式,要匹配的字符串)if re.match(r'py\B', li):print(li) # python# findall() s = "apple Apple APPLE" print(re.findall(r'apple', s)) # ['apple'] print(re.findall(r'apple', s, re.I)) # ['apple', 'Apple', 'APPLE']# sub()查找并替換 print(re.sub('a', 'A', 'abcdacdl')) # AbcdAcdl5、實踐案例
? ? 我們以豆瓣https://movie.douban.com/top250網站為例,去爬取電影信息。
5.1、第一步使用urllib庫獲取網頁
? ? 首先,我們分析一下這個網頁的結構,是一個還算比較規則的網頁,每頁25條,一共10頁。
? ? 我們點擊第一頁:url =?https://movie.douban.com/top250?start=0&filter=
? ? 我們點擊第二頁:url =?https://movie.douban.com/top250?start=25&filter=
? ? 我們點擊第三頁:url =?https://movie.douban.com/top250?start=50&filter=
import urllib.request, urllib.error# 定義基礎url,發現規律,每頁最后變動的是start=后面的數字 baseurl = "https://movie.douban.com/top250?start="# 定義一個函數getHtmlByURL,得到指定url網頁的內容 def geturl(url):# 自定義headers(偽裝,告訴豆瓣服務器,我們是什么類型的機器,以免被反爬蟲)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'}# 利用Request類來構造自定義頭的請求req = urllib.request.Request(url, headers=headers)# 定義一個接收變量,用于接收html = ""try:# urlopen()方法的參數,發送給服務器并接收響應resp = urllib.request.urlopen(req)# urlopen()獲取頁面內容,返回的數據格式為bytes類型,需要decode()解碼,轉換成str類型html = resp.read().decode("utf-8")except urllib.error.URLError as e:if hasattr(e, "code"):print(e.code)if hasattr(e, "reason"):print(e.reason)return htmldef main():print(geturl(baseurl + "0"))if __name__ == "__main__":main()? ? 第一步:我們已經成功獲取到了指定的網頁內容;
5.2、第二步使用BeautifulSoup和re庫解析數據
5.2.1、定位數據塊
? ? 我們發現,我們需要的數據都在<li></li>標簽中一個叫<div class="item"></div>中
import urllib.request, urllib.error from bs4 import BeautifulSoup import re # 定義基礎url,發現規律,每頁最后變動的是start=后面的數字 baseurl = "https://movie.douban.com/top250?start="# 定義一個函數getHtmlByURL,得到指定url網頁的內容 def geturl(url):# 自定義headers(偽裝,告訴豆瓣服務器,我們是什么類型的機器,以免被反爬蟲)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'}# 利用Request類來構造自定義頭的請求req = urllib.request.Request(url, headers=headers)# 定義一個接收變量,用于接收html = ""try:# urlopen()方法的參數,發送給服務器并接收響應resp = urllib.request.urlopen(req)# urlopen()獲取頁面內容,返回的數據格式為bytes類型,需要decode()解碼,轉換成str類型html = resp.read().decode("utf-8")except urllib.error.URLError as e:if hasattr(e, "code"):print(e.code)if hasattr(e, "reason"):print(e.reason)return html# 定義一個函數,并解析這個網頁 def analysisData(url):# 獲取指定網頁html = geturl(url)# 指定解析器解析html,得到BeautifulSoup對象soup = BeautifulSoup(html, "html5lib")# 定位我們的數據塊在哪for item in soup.findAll('div', class_="item"):print(item)return "" def main():print(analysisData(baseurl + "0"))if __name__ == "__main__":main()輸出的第一個數據塊:
<div class="item"><div class="pic"><em class="">1</em><a href="https://movie.douban.com/subject/1292052/"><img alt="肖申克的救贖" class="" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg" width="100"/></a></div><div class="info"><div class="hd"><a class="" href="https://movie.douban.com/subject/1292052/"><span class="title">肖申克的救贖</span><span class="title">?/?The Shawshank Redemption</span><span class="other">?/?月黑高飛(港) / 刺激1995(臺)</span></a><span class="playable">[可播放]</span></div><div class="bd"><p class="">導演: 弗蘭克·德拉邦特 Frank Darabont???主演: 蒂姆·羅賓斯 Tim Robbins /...<br/>1994?/?美國?/?犯罪 劇情</p><div class="star"><span class="rating5-t"></span><span class="rating_num" property="v:average">9.7</span><span content="10.0" property="v:best"></span><span>2390982人評價</span></div><p class="quote"><span class="inq">希望讓人自由。</span></p></div></div></div> <div class="item">5.2.2、使用正則解析數據塊
import urllib.request, urllib.error from bs4 import BeautifulSoup import re# 定義基礎url,發現規律,每頁最后變動的是start=后面的數字 baseurl = "https://movie.douban.com/top250?start="# 定義一個函數getHtmlByURL,得到指定url網頁的內容 def geturl(url):# 自定義headers(偽裝,告訴豆瓣服務器,我們是什么類型的機器,以免被反爬蟲)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'}# 利用Request類來構造自定義頭的請求req = urllib.request.Request(url, headers=headers)# 定義一個接收變量,用于接收html = ""try:# urlopen()方法的參數,發送給服務器并接收響應resp = urllib.request.urlopen(req)# urlopen()獲取頁面內容,返回的數據格式為bytes類型,需要decode()解碼,轉換成str類型html = resp.read().decode("utf-8")except urllib.error.URLError as e:if hasattr(e, "code"):print(e.code)if hasattr(e, "reason"):print(e.reason)return html# 定義正則對象獲取指定的內容 # 提取鏈接(鏈接的格式都是<a href="開頭的) findLink = re.compile(r'<a href="(.*?)">') # 提取圖片 findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S) # re.S讓 '.' 特殊字符匹配任何字符,包括換行符; # 提取影片名稱 findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片評分 findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取評價人數 findJudge = re.compile(r'<span>(\d*)人評價</span>') # 提取簡介 inq = re.compile(r'<span class="inq">(.*)</span>') # 提取相關內容 findBd = re.compile(r'<p class="">(.*)</p>(.*)<div', re.S)# 定義一個函數,并解析這個網頁 def analysisData(baseurl):# 獲取指定網頁html = geturl(baseurl)# 指定解析器解析html,得到BeautifulSoup對象soup = BeautifulSoup(html, "html5lib")dataList = []# 定位我們的數據塊在哪for item in soup.find_all('div', class_="item"):# item 是 bs4.element.Tag 對象,這里將其轉換成字符串來處理item = str(item)# 定義一個列表 來存儲每一個電影解析的內容data = []# findall返回的是一個列表,這里提取鏈接link = re.findall(findLink, item)[0]data.append(link) # 添加鏈接img = re.findall(findImgSrc, item)[0]data.append(img) # 添加圖片鏈接title = re.findall(findTitle, item)# 一般都有一個中文名 一個外文名if len(title) == 2:# ['肖申克的救贖', '\xa0/\xa0The Shawshank Redemption']titlename = title[0] + title[1].replace(u'\xa0', '')else:titlename = title[0] + ""data.append(titlename) # 添加標題pf = re.findall(findRating, item)[0]data.append(pf)pjrs = re.findall(findJudge, item)[0]data.append(pjrs)# 有的可能沒有inqInfo = re.findall(inq, item)if len(inqInfo) == 0:data.append(" ")else:data.append(inqInfo[0])bd = re.findall(findBd, item)[0]# [('\n 導演: 弗蘭克·德拉邦特 Frank Darabont\xa0\xa0\xa0主演: 蒂姆·羅賓斯 Tim Robbins /...<br/>\n 1994\xa0/\xa0美國\xa0/\xa0犯罪 劇情\n ', '\n\n \n ')]bd[0].replace(u'\xa0', '').replace('<br/>', '')bd = re.sub('<\\s*b\\s*r\\s*/\\s*>', "", bd[0])bd = re.sub('(\\s+)?', '', bd)data.append(bd)dataList.append(data)return dataListdef main():print(analysisData(baseurl + "0"))if __name__ == "__main__":main()第一頁解析結果:后面需要對analysisData稍加改造,將豆瓣Top250的10頁進行處理
[['https://movie.douban.com/subject/1292052/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg', '肖申克的救贖/The Shawshank Redemption', '9.7', '2391074', '希望讓人自由。', '導演:弗蘭克·德拉邦特FrankDarabont主演:蒂姆·羅賓斯TimRobbins/...1994/美國/犯罪劇情'], ['https://movie.douban.com/subject/1291546/', 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2561716440.jpg', '霸王別姬', '9.6', '1780355', '風華絕代。', '導演:陳凱歌KaigeChen主演:張國榮LeslieCheung/張豐毅FengyiZha...1993/中國大陸中國香港/劇情愛情同性'], ['https://movie.douban.com/subject/1292720/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2372307693.jpg', '阿甘正傳/Forrest Gump', '9.5', '1800723', '一部美國近現代史。', '導演:羅伯特·澤米吉斯RobertZemeckis主演:湯姆·漢克斯TomHanks/...1994/美國/劇情愛情'], ['https://movie.douban.com/subject/1295644/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p511118051.jpg', '這個殺手不太冷/Léon', '9.4', '1971155', '怪蜀黍和小蘿莉不得不說的故事。', '導演:呂克·貝松LucBesson主演:讓·雷諾JeanReno/娜塔莉·波特曼...1994/法國美國/劇情動作犯罪'], ['https://movie.douban.com/subject/1292722/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p457760035.jpg', '泰坦尼克號/Titanic', '9.4', '1762280', '失去的才是永恒的。 ', '導演:詹姆斯·卡梅隆JamesCameron主演:萊昂納多·迪卡普里奧Leonardo...1997/美國墨西哥澳大利亞加拿大/劇情愛情災難'], ['https://movie.douban.com/subject/1292063/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2578474613.jpg', '美麗人生/La vita è bella', '9.6', '1105760', '最美的謊言。', '導演:羅伯托·貝尼尼RobertoBenigni主演:羅伯托·貝尼尼RobertoBeni...1997/意大利/劇情喜劇愛情戰爭'], ['https://movie.douban.com/subject/1291561/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2557573348.jpg', '千與千尋/千と千尋の神隠し', '9.4', '1877996', '最好的宮崎駿,最好的久石讓。 ', '導演:宮崎駿HayaoMiyazaki主演:柊瑠美RumiH?ragi/入野自由Miy...2001/日本/劇情動畫奇幻'], ['https://movie.douban.com/subject/1295124/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p492406163.jpg', "辛德勒的名單/Schindler's List", '9.5', '918645', '拯救一個人,就是拯救整個世界。', '導演:史蒂文·斯皮爾伯格StevenSpielberg主演:連姆·尼森LiamNeeson...1993/美國/劇情歷史戰爭'], ['https://movie.douban.com/subject/3541415/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2616355133.jpg', '盜夢空間/Inception', '9.3', '1734973', '諾蘭給了我們一場無法盜取的夢。', '導演:克里斯托弗·諾蘭ChristopherNolan主演:萊昂納多·迪卡普里奧Le...2010/美國英國/劇情科幻懸疑冒險'], ['https://movie.douban.com/subject/3011091/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p524964039.jpg', "忠犬八公的故事/Hachi: A Dog's Tale", '9.4', '1192778', '永遠都不能忘記你所愛的人。', '導演:萊塞·霍爾斯道姆LasseHallstr?m主演:理查·基爾RichardGer...2009/美國英國/劇情'], ['https://movie.douban.com/subject/1889243/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2614988097.jpg', '星際穿越/Interstellar', '9.3', '1408128', '愛是一種力量,讓我們超越時空感知它的存在。', '導演:克里斯托弗·諾蘭ChristopherNolan主演:馬修·麥康納MatthewMc...2014/美國英國加拿大/劇情科幻冒險'], ['https://movie.douban.com/subject/1292064/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p479682972.jpg', '楚門的世界/The Truman Show', '9.3', '1325913', '如果再也不能見到你,祝你早安,午安,晚安。', '導演:彼得·威爾PeterWeir主演:金·凱瑞JimCarrey/勞拉·琳妮Lau...1998/美國/劇情科幻'], ['https://movie.douban.com/subject/1292001/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2574551676.jpg', "海上鋼琴師/La leggenda del pianista sull'oceano", '9.3', '1409712', '每個人都要走一條自己堅定了的路,就算是粉身碎骨。 ', '導演:朱塞佩·托納多雷GiuseppeTornatore主演:蒂姆·羅斯TimRoth/...1998/意大利/劇情音樂'], ['https://movie.douban.com/subject/3793023/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p579729551.jpg', '三傻大鬧寶萊塢/3 Idiots', '9.2', '1583056', '英俊版憨豆,高情商版謝耳朵。', '導演:拉庫馬·希拉尼RajkumarHirani主演:阿米爾·汗AamirKhan/卡...2009/印度/劇情喜劇愛情歌舞'], ['https://movie.douban.com/subject/2131459/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p1461851991.jpg', '機器人總動員/WALL·E', '9.3', '1113357', '小瓦力,大人生。', '導演:安德魯·斯坦頓AndrewStanton主演:本·貝爾特BenBurtt/艾麗...2008/美國/科幻動畫冒險'], ['https://movie.douban.com/subject/1291549/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p1910824951.jpg', '放牛班的春天/Les choristes', '9.3', '1098339', '天籟一般的童聲,是最接近上帝的存在。 ', '導演:克里斯托夫·巴拉蒂ChristopheBarratier主演:熱拉爾·朱尼奧Gé...2004/法國瑞士德國/劇情喜劇音樂'], ['https://movie.douban.com/subject/1307914/', 'https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2564556863.jpg', '無間道/無間道', '9.3', '1074152', '香港電影史上永不過時的杰作。', '導演:劉偉強/麥兆輝主演:劉德華/梁朝偉/黃秋生2002/中國香港/劇情犯罪驚悚'], ['https://movie.douban.com/subject/25662329/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2614500649.jpg', '瘋狂動物城/Zootopia', '9.2', '1555912', '迪士尼給我們營造的烏托邦就是這樣,永遠善良勇敢,永遠出乎意料。', '導演:拜倫·霍華德ByronHoward/瑞奇·摩爾RichMoore主演:金妮弗·...2016/美國/喜劇動畫冒險'], ['https://movie.douban.com/subject/1292213/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2455050536.jpg', '大話西游之大圣娶親/西遊記大結局之仙履奇緣', '9.2', '1283551', '一生所愛。', '導演:劉鎮偉JeffreyLau主演:周星馳StephenChow/吳孟達ManTatNg...1995/中國香港中國大陸/喜劇愛情奇幻古裝'], ['https://movie.douban.com/subject/5912992/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p1363250216.jpg', '熔爐/???', '9.3', '778782', '我們一路奮戰不是為了改變世界,而是為了不讓世界改變我們。', '導演:黃東赫Dong-hyukHwang主演:孔侑YooGong/鄭有美Yu-miJung/...2011/韓國/劇情'], ['https://movie.douban.com/subject/1291841/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p616779645.jpg', '教父/The Godfather', '9.3', '781422', '千萬不要記恨你的對手,這樣會讓你失去理智。', '導演:弗朗西斯·福特·科波拉FrancisFordCoppola主演:馬龍·白蘭度M...1972/美國/劇情犯罪'], ['https://movie.douban.com/subject/1849031/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2614359276.jpg', '當幸福來敲門/The Pursuit of Happyness', '9.1', '1273152', '平民勵志片。 ', '導演:加布里爾·穆奇諾GabrieleMuccino主演:威爾·史密斯WillSmith...2006/美國/劇情傳記家庭'], ['https://movie.douban.com/subject/1291560/', 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2540924496.jpg', '龍貓/となりのトトロ', '9.2', '1062785', '人人心中都有個龍貓,童年就永遠不會消失。', '導演:宮崎駿HayaoMiyazaki主演:日高法子NorikoHidaka/坂本千夏Ch...1988/日本/動畫奇幻冒險'], ['https://movie.douban.com/subject/3319755/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p501177648.jpg', '怦然心動/Flipped', '9.1', '1511459', '真正的幸福是來自內心深處。', '導演:羅伯·萊納RobReiner主演:瑪德琳·卡羅爾MadelineCarroll/卡...2010/美國/劇情喜劇愛情'], ['https://movie.douban.com/subject/1296141/', 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p1505392928.jpg', '控方證人/Witness for the Prosecution', '9.6', '378892', '比利·懷德滿分作品。', '導演:比利·懷爾德BillyWilder主演:泰隆·鮑華TyronePower/瑪琳·...1957/美國/劇情犯罪懸疑']]5.3、將數據導出excel
import urllib.request, urllib.error from bs4 import BeautifulSoup import re import xlwt# 定義基礎url,發現規律,每頁最后變動的是start=后面的數字 baseurl = "https://movie.douban.com/top250?start="# 定義一個函數getHtmlByURL,得到指定url網頁的內容 def geturl(url):# 自定義headers(偽裝,告訴豆瓣服務器,我們是什么類型的機器,以免被反爬蟲)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'}# 利用Request類來構造自定義頭的請求req = urllib.request.Request(url, headers=headers)# 定義一個接收變量,用于接收html = ""try:# urlopen()方法的參數,發送給服務器并接收響應resp = urllib.request.urlopen(req)# urlopen()獲取頁面內容,返回的數據格式為bytes類型,需要decode()解碼,轉換成str類型html = resp.read().decode("utf-8")except urllib.error.URLError as e:if hasattr(e, "code"):print(e.code)if hasattr(e, "reason"):print(e.reason)return html# 定義正則對象獲取指定的內容 # 提取鏈接(鏈接的格式都是<a href="開頭的) findLink = re.compile(r'<a href="(.*?)">') # 提取圖片 findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S) # re.S讓 '.' 特殊字符匹配任何字符,包括換行符; # 提取影片名稱 findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片評分 findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取評價人數 findJudge = re.compile(r'<span>(\d*)人評價</span>') # 提取簡介 inq = re.compile(r'<span class="inq">(.*)</span>') # 提取相關內容 findBd = re.compile(r'<p class="">(.*)</p>(.*)<div', re.S)# 定義接收10頁的列表 dataList = []# 定義一個函數,并解析這個網頁 def analysisData(baseurl):# 獲取指定網頁for i in range(0, 10): # 獲取網頁選項的函數,10次url = baseurl + str(i * 25)html = geturl(url)# 指定解析器解析html,得到BeautifulSoup對象soup = BeautifulSoup(html, "html5lib")# 定位我們的數據塊在哪for item in soup.find_all('div', class_="item"):# item 是 bs4.element.Tag 對象,這里將其轉換成字符串來處理item = str(item)# 定義一個列表 來存儲每一個電影解析的內容data = []# findall返回的是一個列表,這里提取鏈接link = re.findall(findLink, item)[0]data.append(link) # 添加鏈接img = re.findall(findImgSrc, item)[0]data.append(img) # 添加圖片鏈接title = re.findall(findTitle, item)# 一般都有一個中文名 一個外文名if len(title) == 2:# ['肖申克的救贖', '\xa0/\xa0The Shawshank Redemption']titlename = title[0] + title[1].replace(u'\xa0', '')else:titlename = title[0] + ""data.append(titlename) # 添加標題pf = re.findall(findRating, item)[0]data.append(pf)pjrs = re.findall(findJudge, item)[0]data.append(pjrs)inqInfo = re.findall(inq, item)if len(inqInfo) == 0:data.append(" ")else:data.append(inqInfo[0])bd = re.findall(findBd, item)[0]# [('\n 導演: 弗蘭克·德拉邦特 Frank Darabont\xa0\xa0\xa0主演: 蒂姆·羅賓斯 Tim Robbins /...<br/>\n 1994\xa0/\xa0美國\xa0/\xa0犯罪 劇情\n ', '\n\n \n ')]bd[0].replace(u'\xa0', '').replace('<br/>', '')bd = re.sub('<\\s*b\\s*r\\s*/\\s*>', "", bd[0])bd = re.sub('(\\s+)?', '', bd)data.append(bd)dataList.append(data)return dataListdef main():analysisData(baseurl)savepath = "C:\\Users\\Administrator\\Desktop\\python_3.8.5\\豆瓣250.xls"book = xlwt.Workbook(encoding="utf-8", style_compression=0) # 創建Workbook對象sheet = book.add_sheet("豆瓣電影Top250", cell_overwrite_ok=True) # 創建工作表col = ("電影詳情鏈接", "圖片鏈接", "電影中/外文名", "評分", "評論人數", "概況", "相關信息")print(len(dataList))for i in range(0, 7):sheet.write(0, i, col[i])for i in range(0, 250):print('正在保存第'+str((i+1))+'條')data = dataList[i]for j in range(len(data)):sheet.write(i + 1, j, data[j])book.save(savepath)if __name__ == "__main__":main()最終效果:
總結
以上是生活随笔為你收集整理的python爬虫详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两个主机通信过程
- 下一篇: python学习---语法