1.安裝BeautifulSoup4
easy_install安裝方式,easy_install需要提前安裝
1 | easy_install beautifulsoup4
|
pip安裝方式,pip也需要提前安裝.此外PyPi中還有一個(gè)名字是 BeautifulSoup 的包,那是 Beautiful Soup3 的發(fā)布版本.在這里不建議安裝.
1 | pip install beautifulsoup4
|
Debain或ubuntu安裝方式
1 | apt-get install Python-bs4
|
你也可以通過源碼安裝,下載BS4源碼
2.小試牛刀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # coding=utf-8
'''
@通過BeautifulSoup下載百度貼吧圖片
'''
import urllib
from bs4 import BeautifulSoup
# 下載網(wǎng)頁
html = urllib.urlopen(url)
content = html.read()
html.close()
# 使用BeautifulSoup匹配圖片
html_soup = BeautifulSoup(content)
# 相較通過正則表達(dá)式去匹配,BeautifulSoup提供了一個(gè)更簡單靈活的方式
all_img_links = html_soup.findAll( 'img' , class_ = 'BDE_Image' )
# 接下來就是老生常談的下載圖片
img_counter = 1
for img_link in all_img_links:
img_name = '%s.jpg' % img_counter
urllib.urlretrieve(img_link[ 'src' ], img_name)
img_counter + = 1
|
很簡單,代碼注釋里面已經(jīng)解釋的很清楚了.BeautifulSoup提供了一個(gè)更簡單靈活的方式,去分析網(wǎng)站源碼,更快獲取圖片link.
3.爬取實(shí)例
3.1基本的抓取技術(shù)
在寫一個(gè)爬蟲腳本時(shí),,第一件事情就是手動觀察要抓取的頁面來確定數(shù)據(jù)如何定位,。
首先,我們要看一看在 http:///category/50/pycon-us-2014 上的 PyCon 大會視頻列表,。檢查這個(gè)頁面的 HTML 源代碼我們發(fā)現(xiàn)視頻列表的結(jié)果差不多是長這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < 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 >
|
那么第一個(gè)任務(wù)就是加載這個(gè)頁面,,然后抽取每個(gè)單獨(dú)頁面的鏈接,因?yàn)榈?YouTube 視頻的鏈接都在這些單獨(dú)頁面上,。
使用requests來加載一個(gè) web 頁面是非常簡單的:
就是它!在這個(gè)函數(shù)返回后就能從response.text中獲得這個(gè)頁面的 HTML ,。
下一個(gè)任務(wù)是抽取每一個(gè)單獨(dú)視頻頁面的鏈接,。通過 BeautifulSoup 使用 CSS 選擇器語法就能完成它,如果你是客戶端開發(fā)者的話你可能對這會很熟悉,。
為了獲得這些鏈接,,我們要使用一個(gè)選擇器,它能抓取在每一個(gè) id 為video-summary-data的<div>中所有的<a>元素,。由于每個(gè)視頻都有幾個(gè)<a>元素,,我們將只保留那些 URL 以/video開頭的<a>元素,這些就是唯一的單獨(dú)視頻頁面,。實(shí)現(xiàn)上述標(biāo)準(zhǔn)的 CSS 選擇器是div.video-summary-data a[href^=/video],。下面的代碼片段通過 BeautifulSoup 使用這個(gè)選擇器來獲得指向視頻頁面的<a>元素:
1 2 3 | import bs4
soup = bs4.BeautifulSoup(response.text)
links = soup.select( 'div.video-summary-data a[href^=/video]' )
|
因?yàn)槲覀冋嬲P(guān)心的是這個(gè)鏈接本身而不是包含它的<a>元素,我們可以使用列表解析來改善上述代碼,。
links = [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]
現(xiàn)在,,我們已經(jīng)有了一個(gè)包含所有鏈接的數(shù)組,這些鏈接指向了每個(gè)單獨(dú)頁面,。
下面這段腳本整理了目前我們提到的所有技術(shù):
1 2 3 4 5 6 7 8 9 10 11 12 | import requests
import bs4
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())
|
如果你運(yùn)行上面這段腳本你將會獲得一個(gè)滿是 URL 的數(shù)組?,F(xiàn)在我們需要去解析每個(gè) URL 以獲得更多關(guān)于每場 PyCon 會議的信息。
3.2抓取相連頁面
下一步是加載我們的 URL 數(shù)組中每一個(gè)頁面,。如果你想要看看這些頁面長什么樣的話,,這兒是個(gè)樣例:http:///video/2668/writing-restful-web-services-with-flask。沒錯(cuò),,那就是我,,那是我會議中的一個(gè)!
從這些頁面我們可以抓取到會議的標(biāo)題,,在頁面的頂部能看到它,。我們也可以從側(cè)邊欄獲得演講者的姓名和 YouTube 的鏈接,側(cè)邊欄在嵌入視頻的右下方,。獲取這些元素的代碼展示在下方:
1 2 3 4 5 6 7 | 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.]' )[ 0 ].get_text() |
關(guān)于這個(gè)函數(shù)需要注意的一些事情:
從首頁抓取的 URL 是相對路徑,,所以root_url需要加到前面。
大會標(biāo)題是從 id 為videobox的<div>里的<h3>元素中獲得的。注意[0]是必須的,,因?yàn)檎{(diào)用select()返回的是一個(gè)數(shù)組,,即使只有一個(gè)匹配。
演講者的姓名和 YouTube 鏈接的獲取方式與首頁上的鏈接獲取方式類似,。
現(xiàn)在就剩下從每個(gè)視頻的 YouTube 頁面抓取觀看數(shù)了,。接著上面的函數(shù)寫下去其實(shí)是非常簡單的。同樣,,我們也可以抓取 like 數(shù)和 dislike 數(shù),。
1 2 3 4 5 6 7 8 9 10 11 | 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
|
上述調(diào)用soup.select()函數(shù),使用指定了 id 名字的選擇器,,采集到了視頻的統(tǒng)計(jì)數(shù)據(jù),。但是元素的文本需要被處理一下才能變成數(shù)字??紤]觀看數(shù)的例子,,在 YouTube 上顯示的是"1,344 views"。用一個(gè)空格分開(split)數(shù)字和文本后,,只有第一部分是有用的,。由于數(shù)字里有逗號,可以用正則表達(dá)式過濾掉任何不是數(shù)字的字符,。
為了完成爬蟲,,下面的函數(shù)調(diào)用了之前提到的所有代碼:
1 2 3 4 | 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)
|
3.3并行處理
上面到目前為止的腳本工作地很好,但是有一百多個(gè)視頻它就要跑個(gè)一會兒了,。事實(shí)上我們沒做什么工作,,大部分時(shí)間都浪費(fèi)在了下載頁面上,在這段時(shí)間腳本時(shí)被阻塞的,。如果腳本能同時(shí)跑多個(gè)下載任務(wù),,可能就會更高效了,是嗎,?
回顧當(dāng)時(shí)寫一篇使用 Node.js 的爬蟲文章的時(shí)候,,并發(fā)性是伴隨 JavaScript 的異步特性自帶來的。使用 Python 也能做到,,不過需要顯示地指定一下,。像這個(gè)例子,我將開啟一個(gè)擁有8個(gè)可并行化進(jìn)程的進(jìn)程池,。代碼出人意料的簡潔:
1 2 3 4 5 6 | from multiprocessing import Pool
def 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個(gè)工作進(jìn)程等待分配任務(wù)運(yùn)行,。為什么是8個(gè)?這是我電腦上核數(shù)的兩倍,。當(dāng)時(shí)實(shí)驗(yàn)不同大小的進(jìn)程池時(shí),,我發(fā)現(xiàn)這是最佳的大小,。小于8個(gè)使腳本跑的太慢,多于8個(gè)也不會讓它更快,。
調(diào)用pool.map()類似于調(diào)用常規(guī)的map(),,它將會對第二個(gè)參數(shù)指定的迭代變量中的每個(gè)元素調(diào)用一次第一個(gè)參數(shù)指定的函數(shù)。最大的不同是,,它將發(fā)送這些給進(jìn)程池所擁有的進(jìn)程運(yùn)行,,所以在這個(gè)例子中八個(gè)任務(wù)將會并行運(yùn)行。
節(jié)省下來的時(shí)間是相當(dāng)大的,。在我的電腦上,,第一個(gè)版本的腳本用了75秒完成,然而進(jìn)程池的版本做了同樣的工作只用了16秒,!
3.4完成爬蟲腳本
我最終版本的爬蟲腳本在獲得數(shù)據(jù)后還做了更多的事情,。
我添加了一個(gè)--sort命令行參數(shù)去指定一個(gè)排序標(biāo)準(zhǔn),可以指定views,,likes或者dislikes。腳本將會根據(jù)指定屬性對結(jié)果數(shù)組進(jìn)行遞減排序,。另一個(gè)參數(shù),,--max代表了要顯示的結(jié)果數(shù)的個(gè)數(shù),萬一你只想看排名靠前的幾條而已,。最后,,我還添加了一個(gè)--csv選項(xiàng),為了可以輕松地將數(shù)據(jù)導(dǎo)到電子制表軟件中,,可以指定數(shù)據(jù)以 CSV 格式打印出來,,而不是表對齊格式。
完整腳本顯示在下方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | import argparse
import re
from multiprocessing import Pool
import requests
import bs4
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]' )]
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.]' )[ 0 ].get_text() 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
def parse_args():
parser = argparse.ArgumentParser(description = 'Show PyCon 2014 video statistics.' )
parser.add_argument( '--sort' , metavar = 'FIELD' , choices = [ 'views' , 'likes' , 'dislikes' ],
default = 'views' ,
help = 'sort by the specified field. Options are views, likes and dislikes.' )
parser.add_argument( '--max' , metavar = 'MAX' , type = int , help = 'show the top MAX entries only.' )
parser.add_argument( '--csv' , action = 'store_true' , default = False ,
help = 'output the data in CSV format.' )
parser.add_argument( '--workers' , type = int , default = 8 ,
help = 'number of workers to use, 8 by default.' )
return parser.parse_args()
def show_video_stats(options):
pool = Pool(options.workers)
video_page_urls = get_video_page_urls()
results = sorted (pool. map (get_video_data, video_page_urls), key = lambda video: video[options.sort],
reverse = True )
max = options. max
if max is None or max > len (results):
max = len (results)
if options.csv:
print (u '"title","speakers", "views","likes","dislikes"' )
else :
print (u 'Views +1 -1 Title (Speakers)' )
for i in range ( max ):
if options.csv:
print (u '"{0}","{1}",{2},{3},{4}' . format (
results[i][ 'title' ], ', ' .join(results[i][ 'speakers' ]), results[i][ 'views' ],
results[i][ 'likes' ], results[i][ 'dislikes' ]))
else :
print (u '{0:5d} {1:3d} {2:3d} {3} ({4})' . format (
results[i][ 'views' ], results[i][ 'likes' ], results[i][ 'dislikes' ], results[i][ 'title' ],
', ' .join(results[i][ 'speakers' ])))
if __name__ = = '__main__' :
show_video_stats(parse_args())
|
下方輸出的是在我寫完代碼時(shí)前25個(gè)觀看數(shù)最多的會議:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | (venv) $ python pycon-scraper.py --sort views --max 25 --workers 8
Views +1 -1 Title (Speakers)
3002 27 0 Keynote - Guido Van Rossum (Guido Van Rossum)
2564 21 0 Computer science fundamentals for self-taught programmers (Justin Abrahms)
2369 17 0 Ansible - Python-Powered Radically Simple IT Automation (Michael Dehaan)
2165 27 6 Analyzing Rap Lyrics with Python (Julie Lavoie)
2158 24 3 Exploring Machine Learning with Scikit-learn (Jake Vanderplas, Olivier Grisel)
2065 13 0 Fast Python, Slow Python (Alex Gaynor)
2024 24 0 Getting Started with Django, a crash course (Kenneth Love)
1986 47 0 It's Dangerous to Go Alone: Battling the Invisible Monsters in Tech (Julie Pagano)
1843 24 0 Discovering Python (David Beazley)
1672 22 0 All Your Ducks In A Row: Data Structures in the Standard Library and Beyond (Brandon Rhodes)
1558 17 1 Keynote - Fernando Pérez (Fernando Pérez)
1449 6 0 Descriptors and Metaclasses - Understanding and Using Python's More Advanced Features (Mike Müller)
1402 12 0 Flask by Example (Miguel Grinberg)
1342 6 0 Python Epiphanies (Stuart Williams)
1219 5 0 0 to 00111100 with web2py (G. Clifford Williams)
1169 18 0 Cheap Helicopters In My Living Room (Ned Jackson Lovely)
1146 11 0 IPython in depth: high productivity interactive and parallel python (Fernando Perez)
1127 5 0 2D/3D graphics with Python on mobile platforms (Niko Skrypnik)
1081 8 0 Generators: The Final Frontier (David Beazley)
1067 12 0 Designing Poetic APIs (Erik Rose)
1064 6 0 Keynote - John Perry Barlow (John Perry Barlow)
1029 10 0 What Is Async, How Does It Work, And When Should I Use It? (A. Jesse Jiryu Davis)
981 11 0 The Sorry State of SSL (Hynek Schlawack)
961 12 2 Farewell and Welcome Home: Python in Two Genders (Naomi Ceder)
958 6 0 Getting Started Testing (Ned Batchelder)
|