· 來源:linux.cn · 作者:Julia Piaskowski · 譯者:Hacker · (本文字數(shù):11235,,閱讀時長大約:14 分鐘) 有很多很棒的書可以幫助你學習 Python ,,但是誰真正讀了這那些大部頭呢,?(劇透:反正不是我)。 許多人覺得教學書籍很有用,,但我通常不會從頭到尾地閱讀一本書來學習,。我是通過做一個項目,努力的弄清楚一些內(nèi)容,,然后再讀另一本書來學習,。因此,暫時丟掉書,,讓我們一起學習 Python,。 接下來是我的第一個 Python 爬取項目的指南。它對 Python 和 HTML 的假定知識要求很低,。這篇文章旨在說明如何使用 Python 的 requests 庫訪問網(wǎng)頁內(nèi)容,,并使用 BeatifulSoup4 庫以及 JSON 和 pandas 庫解析網(wǎng)頁內(nèi)容。我將簡要介紹 Selenium 庫,,但我不會深入研究如何使用該庫——這個主題值得有自己的教程,。最終,我希望向你展示一些技巧和小竅門,,以減少網(wǎng)頁爬取過程中遇到的問題,。 安裝依賴我的 GitHub 存儲庫 中提供了本指南的所有資源。如果需要安裝 Python3 的幫助,,請查看 Linux ,、 Windows 和 Mac 的教程。 $ python3 -m venv$ source venv/bin/activate$ pip install requests bs4 pandas 如果你喜歡使用 JupyterLab ,,則可以使用 notebook 運行所有代碼,。 安裝 JupyterLab 有很多方法,這是其中一種:
為網(wǎng)站抓取項目設定目標現(xiàn)在我們已經(jīng)安裝了依賴項,,但是爬取網(wǎng)頁需要做什么,? 讓我們退一步,確保使目標清晰,。下面是成功完成網(wǎng)頁爬取項目需求列表:
關于 HTML 的備注:HTML 是運行在互聯(lián)網(wǎng)上的“猛獸”,但我們最需要了解的是標簽的工作方式,。標簽是一對由尖括號包圍關鍵詞(一般成對出現(xiàn),,其內(nèi)容在兩個標簽中間)。比如,,這是一個假裝的標簽,,稱為 pro-tip: <pro-tip> All you need to know about html is how tags work </pro-tip> 我們可以通過調(diào)用標簽 pro-tip 來訪問其中的信息(All you need to know…),。本教程將進一步介紹如何查找和訪問標簽,。要進一步了解 HTML 基礎知識,請查看 本文 ,。 網(wǎng)站爬取項目中要找的是什么有些數(shù)據(jù)利用網(wǎng)站爬取采集比利用其他方法更合適,。以下是我認為合適項目的準則: 沒有可用于數(shù)據(jù)(處理)的公共 API。通過 API 抓取結構化數(shù)據(jù)會容易得多,,(所以沒有 API )有助于澄清收集數(shù)據(jù)的合法性和道德性,。而有相當數(shù)量的結構化數(shù)據(jù),并有規(guī)律的,、可重復的格式,,才能證明這種努力的合理性。網(wǎng)頁爬取可能會很痛苦,。BeautifulSoup(bs4)使操作更容易,,但無法避免網(wǎng)站的個別特殊性,需要進行定制,。數(shù)據(jù)的相同格式化不是必須的,,但這確實使事情變得更容易。存在的 “邊際案例”(偏離規(guī)范)越多,,爬取就越復雜,。 免責聲明:我沒有參加過法律培訓;以下內(nèi)容無意作為正式的法律建議,。 關于合法性,,訪問大量有價值信息可能令人興奮,但僅僅因為它是可能的,,并不意味著應該這樣做,。 值得慶幸的是,有一些公共信息可以指導我們的道德規(guī)范和網(wǎng)頁爬取工具,。大多數(shù)網(wǎng)站都有與該網(wǎng)站關聯(lián)的 robots.txt 文件,,指出允許哪些爬取活動,哪些不被允許。它主要用于與搜索引擎(網(wǎng)頁抓取工具的終極形態(tài))進行交互,。然而,,網(wǎng)站上的許多信息都被視為公共信息。因此,,有人將 robots.txt 文件視為一組建議,,而不是具有法律約束力的文檔。 robots.txt 文件并不涉及數(shù)據(jù)的道德收集和使用等主題,。 在開始爬取項目之前,,問自己以下問題:
當我爬取一個網(wǎng)站時,,請確??梢詫λ羞@些問題回答 “否”。 要深入了解這些法律問題,,請參閱 2018 年出版的 Krotov 和 Silva 撰寫的 《Web 爬取的合法性和道德性》 和 Sellars 的 《二十年 Web 爬取和計算機欺詐與濫用法案》 ,。 現(xiàn)在開始爬取網(wǎng)站經(jīng)過上述評估,我想出了一個項目,。我的目標是爬取愛達荷州所有 Family Dollar 商店的地址,。 這些商店在農(nóng)村地區(qū)規(guī)模很大,因此我想了解有多少家這樣的商店,。 起點是 Family Dollar 的位置頁面 愛達荷州 Family Dollar 所在地頁面 首先,,讓我們在 Python 虛擬環(huán)境中加載先決條件。此處的代碼將被添加到一個 Python 文件(如果你想要個名稱,,則為 scraper.py)或在 JupyterLab 的單元格中運行,。
接下來,我們從目標 URL 中請求數(shù)據(jù),。 page = requests.get('https://locations./id/')soup = BeautifulSoup(page.text, 'html.parser') BeautifulSoup 將 HTML 或 XML 內(nèi)容轉換為復雜樹對象,。這是我們將使用的幾種常見對象類型。
當我們查看 requests.get() 輸出時,,還有更多要考慮的問題,。我僅使用 page.text() 將請求的頁面轉換為可讀的內(nèi)容,但是還有其他輸出類型:
我只在使用拉丁字母的純英語網(wǎng)站上操作,。 requests 中的默認編碼設置可以很好地解決這一問題,。然而,除了純英語網(wǎng)站之外,,就是更大的互聯(lián)網(wǎng)世界,。為了確保 requests 正確解析內(nèi)容,你可以設置文本的編碼:
仔細研究 BeautifulSoup 標簽,,我們看到:
確定如何提取相應內(nèi)容警告:此過程可能令人沮喪。 網(wǎng)站爬取過程中的提取可能是一個令人生畏的充滿了誤區(qū)的過程,。我認為解決此問題的最佳方法是從一個有代表性的示例開始然后進行擴展(此原理對于任何編程任務都是適用的),。查看頁面的 HTML 源代碼至關重要。有很多方法可以做到這一點,。 你可以在終端中使用 Python 查看頁面的整個源代碼(不建議使用),。運行此代碼需要你自擔風險: print(soup.prettify()) 雖然打印出頁面的整個源代碼可能適用于某些教程中顯示的玩具示例,但大多數(shù)現(xiàn)代網(wǎng)站的頁面上都有大量內(nèi)容,。甚至 404 頁面也可能充滿了頁眉,、頁腳等代碼。 通常,,在你喜歡的瀏覽器中通過 “查看頁面源代碼” 來瀏覽源代碼是最容易的(單擊右鍵,,然后選擇 “查看頁面源代碼” )。這是找到目標內(nèi)容的最可靠方法(稍后我將解釋原因),。 Family Dollar 頁面源代碼 在這種情況下,,我需要在這個巨大的 HTML 海洋中找到我的目標內(nèi)容 —— 地址、城市,、州和郵政編碼,。通常,對頁面源(ctrl+F)的簡單搜索就會得到目標位置所在的位置,。一旦我實際看到目標內(nèi)容的示例(至少一個商店的地址),,便會找到將該內(nèi)容與其他內(nèi)容區(qū)分開的屬性或標簽,。 首先,我需要在愛達荷州 Family Dollar 商店中收集不同城市的網(wǎng)址,,并訪問這些網(wǎng)站以獲取地址信息,。這些網(wǎng)址似乎都包含在 href 標記中。太棒了,!我將嘗試使用 find_all 命令進行搜索:
搜索 href 不會產(chǎn)生任何結果,,該死。這可能是因為 href 嵌套在 itemlist 類中而失敗,。對于下一次嘗試,,請搜索 item_list。由于 class 是 Python 中的保留字,,因此使用 class_ 來作為替代,。soup.find_all() 原來是 bs4 函數(shù)的瑞士軍刀。 dollar_tree_list = soup.find_all(class_ = 'itemlist')for i in dollar_tree_list[:2]: print(i) 有趣的是,,我發(fā)現(xiàn)搜索一個特定類的方法一般是一種成功的方法,。通過找出對象的類型和長度,我們可以了解更多有關對象的信息,。
可以使用 .contents 從 BeautifulSoup “結果集” 中提取內(nèi)容,。這也是創(chuàng)建單個代表性示例的好時機。 example = dollar_tree_list[2] # a representative exampleexample_content = example.contentsprint(example_content) 使用 .attr 查找該對象內(nèi)容中存在的屬性,。注意:.contents 通常會返回一個項目的精確的列表,,因此第一步是使用方括號符號為該項目建立索引。
現(xiàn)在,,我可以看到 href 是一個屬性,,可以像字典項一樣提取它: example_href = example_content['href']print(example_href) 整合網(wǎng)站抓取工具所有的這些探索為我們提供了前進的路徑。這是厘清上面邏輯的一個清理版本,。
輸出的內(nèi)容是一個關于抓取愛達荷州 Family Dollar 商店 URL 的列表,。 也就是說,我仍然沒有獲得地址信息,!現(xiàn)在,,需要抓取每個城市的 URL 以獲得此信息。因此,,我們使用一個具有代表性的示例重新開始該過程,。 page2 = requests.get(city_hrefs[2]) # again establish a representative examplesoup2 = BeautifulSoup(page2.text, 'html.parser') Family Dollar 地圖和代碼 地址信息嵌套在 type='application/ld+json' 里。經(jīng)過大量的地理位置抓取之后,,我開始認識到這是用于存儲地址信息的一般結構,。幸運的是,soup.find_all() 開啟了利用 type 搜索,。
地址信息在第二個列表成員中,!原來如此,! 使用 .contents 提取(從第二個列表項中)內(nèi)容(這是過濾后的合適的默認操作),。同樣,,由于輸出的內(nèi)容是一個列表,因此我為該列表項建立了索引: arco_contents = arco[1].contents[0]arco_contents 喔,,看起來不錯,。此處提供的格式與 JSON 格式一致(而且,該類型的名稱中確實包含 “json”),。 JSON 對象的行為就像是帶有嵌套字典的字典,。一旦你熟悉利用其去工作,它實際上是一種不錯的格式(當然,,它比一長串正則表達式命令更容易編程),。盡管從結構上看起來像一個 JSON 對象,但它仍然是 bs4 對象,,需要通過編程方式轉換為 JSON 對象才能對其進行訪問:
type(arco_json)print(arco_json) 在該內(nèi)容中,,有一個被調(diào)用的 address 鍵,該鍵要求地址信息在一個比較小的嵌套字典里,??梢赃@樣檢索:
好吧,請大家注意?,F(xiàn)在我可以遍歷存儲愛達荷州 URL 的列表: locs_dict = [] # initialise empty listfor link in city_hrefs: locpage = requests.get(link) # request page info locsoup = BeautifulSoup(locpage.text, 'html.parser') # parse the page's content locinfo = locsoup.find_all(type='application/ld+json') # extract specific element loccont = locinfo[1].contents[0] # get contents from the bs4 element set locjson = json.loads(loccont) # convert to json locaddr = locjson['address'] # get address locs_dict.append(locaddr) # add address to list 用 Pandas 整理我們的網(wǎng)站抓取結果我們在字典中裝載了大量數(shù)據(jù),,但是還有一些額外的無用項,它們會使重用數(shù)據(jù)變得比需要的更為復雜,。要執(zhí)行最終的數(shù)據(jù)組織,我們需要將其轉換為 Pandas 數(shù)據(jù)框架,,刪除不需要的列 @type 和 country,,并檢查前五行以確保一切正常。
確保保存結果??! df.to_csv(locs_df, 'family_dollar_ID_locations.csv', sep = ',', index = False) 我們做到了!所有愛達荷州 Family Dollar 商店都有一個用逗號分隔的列表,。多令人興奮,。 Selenium 和數(shù)據(jù)抓取的一點說明Selenium 是用于與網(wǎng)頁自動交互的常用工具。為了解釋為什么有時必須使用它,,讓我們來看一個使用 Walgreens 網(wǎng)站的示例,。 “檢查元素” 提供了瀏覽器顯示內(nèi)容的代碼: 雖然 “查看頁面源代碼” 提供了有關 requests 將獲得什么內(nèi)容的代碼: 如果這兩個不一致,是有一些插件可以修改源代碼 —— 因此,,應在將頁面加載到瀏覽器后對其進行訪問,。requests 不能做到這一點,,但是 Selenium 可以做到。 Selenium 需要 Web 驅動程序來檢索內(nèi)容,。實際上,,它會打開 Web 瀏覽器,并收集此頁面的內(nèi)容,。Selenium 功能強大 —— 它可以通過多種方式與加載的內(nèi)容進行交互(請閱讀文檔),。使用 Selenium 獲取數(shù)據(jù)后,繼續(xù)像以前一樣使用 BeautifulSoup:
對于 Family Dollar 這種情形,,我不需要 Selenium,,但是當呈現(xiàn)的內(nèi)容與源代碼不同時,我確實會保留使用 Selenium,。 小結總之,,使用網(wǎng)站抓取來完成有意義的任務時:
如果你對答案感到好奇: Family Dollar 位置圖 美國有很多 Family Dollar 商店。 完整的源代碼是: import requestsfrom bs4 import BeautifulSoupimport jsonfrom pandas import DataFrame as dfpage = requests.get('https://www./locations/')soup = BeautifulSoup(page.text, 'html.parser')# find all state linksstate_list = soup.find_all(class_ = 'itemlist')state_links = []for i in state_list: cont = i.contents[0] attr = cont.attrs hrefs = attr['href'] state_links.append(hrefs)# find all city linkscity_links = []for link in state_links: page = requests.get(link) soup = BeautifulSoup(page.text, 'html.parser') familydollar_list = soup.find_all(class_ = 'itemlist') for store in familydollar_list: cont = store.contents[0] attr = cont.attrs city_hrefs = attr['href'] city_links.append(city_hrefs)# to get individual store linksstore_links = []for link in city_links: locpage = requests.get(link) locsoup = BeautifulSoup(locpage.text, 'html.parser') locinfo = locsoup.find_all(type='application/ld+json') for i in locinfo: loccont = i.contents[0] locjson = json.loads(loccont) try: store_url = locjson['url'] store_links.append(store_url) except: pass# get address and geolocation informationstores = []for store in store_links: storepage = requests.get(store) storesoup = BeautifulSoup(storepage.text, 'html.parser') storeinfo = storesoup.find_all(type='application/ld+json') for i in storeinfo: storecont = i.contents[0] storejson = json.loads(storecont) try: store_addr = storejson['address'] store_addr.update(storejson['geo']) stores.append(store_addr) except: pass# final data parsingstores_df = df.from_records(stores)stores_df.drop(['@type', 'addressCountry'], axis = 1, inplace = True)stores_df['Store'] = 'Family Dollar'df.to_csv(stores_df, 'family_dollar_locations.csv', sep = ',', index = False) 作者注釋:本文改編自 2020 年 2 月 9 日在俄勒岡州波特蘭的 我在 PyCascades 的演講 ,。 via: opensource.com |
|