使用 Python 輕鬆抓取網頁__Python

來源:互聯網
上載者:User

[ 翻譯自英文原文:Easy Web Scraping with Python ]

一年多以前我寫了一篇文章「web scraping using Node.js」。今天我重新回顧了這個話題,但是這一次我將使用 Python,這樣這兩種語言所提供的技術就能進行對比和比較。 問題

我敢肯定你知道,我在本月初參加了在蒙特利爾舉辦的 PyCon 大會。所有的演講和教程的視頻都已經發布到 YouTube 上了,目錄在 pyvideo.org。

我認為知道這個大會上的哪些視頻最受歡迎將會是非常有用的,所以我們將要寫一個爬蟲指令碼。這個指令碼將會從 pyvideo.org 上擷取有效視頻列表,然後從每個 YouTube 頁面擷取觀看的統計資料。聽起來很有趣。讓我們開始吧。 工具

在抓取網站中有兩個基本的任務: 載入網頁到一個 string 裡。 從網頁中解析 HTML 來定位感興趣的位置。

Python 為上面兩個任務提供了兩個超棒的工具。我將使用 requests 去載入網頁,用 BeautifulSoup 去做解析。

我們可以把上面兩個包放到一個虛擬環境:

$ mkdir pycon-scraper$ virtualenv venv$ source venv/bin/activate(venv) $ pip install requests beautifulsoup4

如果使用的是 Windows 作業系統,注意上面虛擬環境的啟用命令是不同的,你應該使用venv\Scripts\activate。
基本的抓取技術

在寫一個爬蟲指令碼時,第一件事情就是手動觀察要抓取的頁面來確定資料如何定位。

首先,我們要看一看在 http://pyvideo.org/category/50/pycon-us-2014 上的 PyCon 大會視頻列表。檢查這個頁面的 HTML 原始碼我們發現視頻列表的結果差不多是長這樣的:

<div id="video-summary-content">    <div class="video-summary">    <!-- first video -->        <div class="thumbnail-data">...</div>        <div class="video-summary-data">            <div>                <strong><a href="#link to video page#">#title#</a></strong>            </div>        </div>    </div>    <div class="video-summary">    <!-- second video -->        ...    </div>    ...</div>

那麼第一個任務就是載入這個頁面,然後抽取每個單獨頁面的連結,因為到 YouTube 視頻的連結都在這些單獨頁面上。

使用requests來載入一個 web 頁面是非常簡單的:

import requestsresponse = requests.get('http://pyvideo.org/category/50/pycon-us-2014')

就是它。在這個函數返回後就能從response.text中獲得這個頁面的 HTML 。

下一個任務是抽取每一個單獨視頻頁面的連結。通過 BeautifulSoup 使用 CSS 選取器文法就能完成它,如果你是用戶端開發人員的話你可能對這會很熟悉。

為了獲得這些連結,我們要使用一個選取器,它能抓取在每一個 id 為video-summary-data的<div>中所有的<a>元素。由於每個視頻都有幾個<a>元素,我們將只保留那些 URL 以/video開頭的<a>元素,這些就是唯一的單獨視頻頁面。實現上述標準的 CSS 選取器是div.video-summary-data a[href^=/video]。下面的程式碼片段通過 BeautifulSoup 使用這個選取器來獲得指向視頻頁面的<a>元素:

import bs4soup = bs4.BeautifulSoup(response.text)links = soup.select('div.video-summary-data a[href^=/video]')

因為我們真正關心的是這個連結本身而不是包含它的<a>元素,我們可以使用列表解析來改善上述代碼。

links = [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]

現在,我們已經有了一個包含所有連結的數組,這些連結指向了每個單獨頁面。

下面這段指令碼整理了目前我們提到的所有技術:

import requestsimport bs4root_url = 'http://pyvideo.org'index_url = root_url + '/category/50/pycon-us-2014'def get_video_page_urls():    response = requests.get(index_url)    soup = bs4.BeautifulSoup(response.text)    return [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]print(get_video_page_urls())

如果你運行上面這段指令碼你將會獲得一個滿是 URL 的數組。現在我們需要去解析每個 URL 以獲得更多關於每場 PyCon 會議的資訊。 抓取相連頁面

下一步是載入我們的 URL 數組中每一個頁面。如果你想要看看這些頁面長什麼樣的話,這兒是個範例:http://pyvideo.org/video/2668/writing-restful-web-services-with-flask。沒錯,那就是我,那是我會議中的一個。

從這些頁面我們可以抓取到會議的標題,在頁面的頂部能看到它。我們也可以從側邊欄獲得演講者的姓名和 YouTube 的連結,側邊欄在嵌入視頻的右下方。擷取這些元素的代碼展示在下方:

def get_video_data(video_page_url):    video_data = {}    response = requests.get(root_url + video_page_url)    soup = bs4.BeautifulSoup(response.text)    video_data['title'] = soup.select('div#videobox h3')[0].get_text()    video_data['speakers'] = [a.get_text() for a in soup.select('div#sidebar a[href^=/speaker]')]    video_data['youtube_url'] = soup.select('div#sidebar a[href^=http://www.youtube.com]')[0].get_text()

關於這個函數需要注意的一些事情: 從首頁抓取的 URL 是相對路徑,所以root_url需要加到前面。 大會標題是從 id 為videobox的<div>裡的<h3>元素中獲得的。注意[0]是必須的,因為調用select()返回的是一個數組,即使只有一個匹配。 演講者的姓名和 YouTube 連結的擷取方式與首頁上的連結擷取方式類似。

現在就剩下從每個視頻的 YouTube 頁面抓取觀看數了。接著上面的函數寫下去其實是非常簡單的。同樣,我們也可以抓取 like 數和 dislike 數。

def get_video_data(video_page_url):    # ...    response = requests.get(video_data['youtube_url'])    soup = bs4.BeautifulSoup(response.text)    video_data['views'] = int(re.sub('[^0-9]', '',                                     soup.select('.watch-view-count')[0].get_text().split()[0]))    video_data['likes'] = int(re.sub('[^0-9]', '',                                     soup.select('.likes-count')[0].get_text().split()[0]))    video_data['dislikes'] = int(re.sub('[^0-9]', '',                                         soup.select('.dislikes-count')[0].get_text().split()[0]))    return video_data

上述調用soup.select()函數,使用指定了 id 名字的選取器,採集到了視頻的統計資料。但是元素的文本需要被處理一下才能變成數字。考慮觀看數的例子,在 YouTube 上顯示的是"1,344 views"。用一個空格分開(split)數字和文本後,只有第一部分是有用的。由於數字裡有逗號,可以用Regex過濾掉任何不是數位字元。

為了完成爬蟲,下面的函數調用了之前提到的所有代碼:

def show_video_stats():    video_page_urls = get_video_page_urls()    for video_page_url in video_page_urls:        print get_video_data(video_page_url)
平行處理

上面到目前為止的指令碼工作地很好,但是有一百多個視頻它就要跑個一會兒了。事實上我們沒做什麼工作,大部分時間都浪費在了下載頁面上,在這段時間指令碼時被阻塞的。如果指令碼能同時跑多個下載任務,可能就會更高效了,是嗎。

回顧當時寫一篇使用 Node.js 的爬蟲文章的時候,並發性是伴隨 JavaScript 的非同步特性內建來的。使用 Python 也能做到,不過需要顯示地指定一下。像這個例子,我將開啟一個擁有8個可並行化進程的進程池。代碼出人意料的簡潔:

from multiprocessing import Pooldef show_video_stats(options):    pool = Pool(8)    video_page_urls = get_video_page_urls()    results = pool.map(get_video_data, video_page_urls)

multiprocessing.Pool 類開啟了8個背景工作處理序等待分配任務運行。為什麼是8個。這是我電腦上核心數的兩倍。當時實驗不同大小的進程池時,我發現這是最佳的大小。小於8個使指令碼跑的太慢,多於8個也不會讓它更快。

調用pool.map()類似於調用常規的map(),它將會對第二個參數指定的迭代變數中的每個元素調用一次第一個參數指定的函數。最大的不同是,它將發送這些給進程池所擁有的進程運行,所以在這個例子中八個任務將會並行運行。

節省下來的時間是相當大的。在我的電腦上,第一個版本的指令碼用了75秒完成,然而進程池的版本做了同樣的工作只用了16秒。 完成爬蟲指令碼

我最終版本的爬蟲指令碼在獲得資料後還做了更多的事情。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.