Python3爬蟲實戰(zhàn): requests+正則爬取音樂網站Allmusic

本文同時發(fā)布至我的個人博客,點擊進入我的個人博客閱讀。本博客供技術交流與經驗分享檀轨,可自由轉載。轉載請附帶原文鏈接欺嗤,感謝参萄!

項目背景

AllMusic 是一個關于音樂的元數據資料庫,在1991年由流行文化維護者Michael Erlewine與數學家兼哲學博士Vladimir Bogdanov創(chuàng)立煎饼,目的是成為音樂消費者的導覽讹挎。AllMusic New Releases 是 AllMusic 為用戶提供的一項內容推薦服務,以周為頻次向用戶推薦本周的新音樂/新專輯吆玖,甚至你可以通過郵件的形式來訂閱該內容筒溃。

對于中重度音樂愛好者或習慣聆聽新音樂的人群,AllMusic New Releases 提供了很優(yōu)秀的推薦服務沾乘。作為一個嚴謹專業(yè)音樂資料庫怜奖,AllMusic 提供十分專業(yè)且全面的音樂信息。于此同時翅阵,AllMusic 有一個由若干專家樂評人組成的內容團隊歪玲,每周推薦的都是一些比較具有音樂性或話題性的專輯,同時也提供十分專業(yè)的樂評

作為一名 AllMusic 的用戶掷匠,我對其中的內容質量十分滿意滥崩,但是使用過程中還是有一些不好的體驗:

  1. 由于服務器架設在國外,雖然沒有被墻槐雾,但是網頁加載十分緩慢夭委。
  2. AllMusic 在去年接入廣告服務,需要安裝對應的廣告插件才能正常訪問。

學習了 Python 的基本爬蟲技術后株灸,我決定嘗試一下通過爬蟲技術來規(guī)避這個問題崇摄。基本思路是:爬取最近10周的 AllMusic New Releases 的內容慌烧,獲取專輯圖片(地址)逐抑、藝術家、專輯名屹蚊、風格厕氨、廠牌、評分等基本信息汹粤,并以文本形式存儲于本地命斧,下次需要查看時可以直接查看本地文件。

功能實現

一個原始的爬蟲實現可以分為:抓取頁面 —> 信息提取 —> 格式化輸出/存儲嘱兼,同時国葬,由于我們需要處理10個頁面,所以引入線程池來實現多線程爬蟲能一定程度地優(yōu)化爬蟲性能芹壕。有了基本的方向之后就可以開始編寫程序汇四,這里我們使用最原始的步進式編程策略來完成。

(一)抓取單個頁面

Python 中關于實現頁面抓取的一般有 urllibrequests踢涌, 這里我們選擇 API 更加簡潔的requests 通孽。

def getOnePage(url, headers):
    try:
        rp = requests.get(url=url, headers=headers)
        if rp.status_code == 200:
            return rp.text
        return None
    except RequestException as e:
        print('Request Exception')
        return None

getOnePage()主體上是一個try...except...結構,調用requests.get()獲取指定 url 的 html 代碼睁壁,并以字符串的形式返回背苦;若獲取失敗則獲取函數拋出的RequestException異常,同時要注意 Allmusic 會檢查 get 方法的請求頭堡僻,所以我們需要傳入headers請求頭參數糠惫。

(二)信息析取

這里我們需要爬取兩方面的信息:一是我們需要獲取的New Releases 的內容疫剃;二是需要從網頁中獲取日期信息來構成url(當然也可以直接通過算法計算钉疫,Allmusic 的更新日期是每周的周五)。

析取 New Releases 中的內容

使用Chrome的開發(fā)者工具分析我們需要爬取的網頁巢价,觀察我們關心的字段內容及其所在的標簽牲阁。這里我們使用正則表達式匹配來解析,當然你也可以選擇 BeautifulSoup壤躲、Pyquery 等網頁解析庫城菊。

def parseOnePage(html):
    # use regular expression to get the specified information we want. It do not work well on 'artist' file, so we
    # we have to process 'artist' after it
    pattern = re.compile('album-cover">.*?img src="(.*?)".*?artist">(.*?)</div>.*?title">.*?>(.*?)</a>.*?label">(.*?)</div>.*?styles">.*?>(.*?)</a>.*?allmusic-rating rating-allmusic-(\d+)">.*?headline-review">(.*?)<div.*?author">(.*?)</div>', re.S)
    items = re.findall(pattern, html)
    for item in items:
        # process the 'artist',in order to remove the html code such as '<a>xxx</a>', I use re.split() func.
        artist = item[1].strip()
        artist = re.split('<.*?>', artist)
        artist = ''.join(artist).strip()
        yield {
            'cover': item[0],
            'artist': artist,
            'title': item[2],
            'label': item[3].strip(),
            'styles': item[4],
            'allmusic-rating': item[5],
            'review': item[6].strip(),
            'author': item[7].strip()[2:],
        }

這里使用正則表達式來解決確實帶來了一定的麻煩,問題在于在匹配artist字段時由于html格式上的不統(tǒng)一給匹配語法帶來了麻煩碉克,無法直接用一次正則匹配解決凌唬。這里最后采用的方法是“先擴大匹配范圍,然后再在后續(xù)處理中過濾不需要的內容”這種思路漏麦。首先客税,第一次通過pattern規(guī)則匹配况褪,我們獲得類似如下格式的artist字段:

...
{'artist': '<a >Jefre Cantu-Ledesma</a>'}
{'artist': 'Various Artists'}
{'artist': '<a }
...

進而,使用re.split('<.*?>', artist)更耻,過濾標簽即可獲得文本內容:

...
{'artist': 'Jefre Cantu-Ledesma'}
{'artist': 'Various Artists'}
{'artist': 'Peacers'}
...

正則表達式的用法技巧性比較強测垛,不停地試錯和調試然后靈活地調用方法才能比較高效地解決問題。正常匹配之后秧均,我們可以嘗試添加如下main()函數測試單網頁的爬取是否正常食侮。

def main():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
        'Host': 'www.allmusic.com'
    }
    url = 'http://www.allmusic.com/newreleases' 
    print(parseOnePage(getOnePage(url, headers)))
    
if __name__ == '__main__':
  main()

獲取日期信息

若單網頁能正常爬取,那么我們可以開始考慮爬取多個網頁目胡。首先分析這些網頁url規(guī)律:

http://www.allmusic.com/newreleases/20170818
http://www.allmusic.com/newreleases/20170811
http://www.allmusic.com/newreleases/20170804
...

不難想到锯七,我們只要獲取所有的日期并以’YYYYMMDD‘的形式添加在基礎url上,就可以得到最終的url誉己。通過一下方法起胰,我們可以從網頁中獲取日期信息:

def getDate(html):
    # get the most recent date and save as a 'datetime'
    pattern = re.compile('week-filter">.*?value="(.*?)".*?selected">', re.S)
    selecteDate = re.findall(pattern, html)[0]
    selecteDatetime = datetime.strptime(selecteDate, '%Y%m%d')
    date = []
    # Allmusic update its information per week so we get information one time for every 7 days. The way to realize it
    # is changing the end of url(such as /20170818 to 20170811)
    for i in range(10):
        i_timedelta = timedelta(7 * i, 0, 0)
        last_datetime = selecteDatetime - i_timedelta
        date.append(datetime.strftime(last_datetime, '%Y%m%d'))
    return date

類似地,也是使用正則匹配的方法巫延。另外效五,這里我在獲取第一個日期字符串后,將其轉為datetime對象炉峰,以使用datetime的相關方法來計算得出剩余九個需要獲取的日期畏妖。

(三)靜態(tài)本地存儲

def writeDown(content):
    with open('AllmusicNewReleasesLast10Week.txt', 'a', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')
        f.close()

寫入txt文件中,實現本地存儲疼阔。

(四)多線程爬取

修改main()函數與文件入口戒劫,將爬取10個網頁的線程加入線程池中,進行多線程爬绕爬取:

def main(offset):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome' +
                      '/60.0.3112.90 Safari/537.36',
        'Host': 'www.allmusic.com'
    }
    url = 'http://www.allmusic.com/newreleases'
    date = getDate(getOnePage(url, headers))
    new_url = url + '/' + date[offset]
    for i in parseOnePage(getOnePage(new_url, headers)):
        print(i)
        writeDown(i)

        
if __name__ == '__main__':
    pool = Pool()
    pool.map(main, [i for i in range(10)])

在控制臺輸出迅细,引入多線程后爬取時間縮短了2-3秒左右,性能明顯提升淘邻。

項目總結

第一次寫爬蟲程序茵典,選擇了使用 requests + 正則的實現方案,主要是為了鞏固基礎技術宾舅。正則表達式雖然強大统阿,但是在實現過程中確實會遇到困難〕镂遥或許使用 BeautifulSoup 一個簡單的標簽選擇就可以實現的解析扶平,用正則來實現可能會繁瑣許多,工具選擇確實對實現效率有很大影響蔬蕊。當然结澄,熟練地使用正則表達式,也能在很多時候很巧妙地解決問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末麻献,一起剝皮案震驚了整個濱河市呼巷,隨后出現的幾起案子,更是在濱河造成了極大的恐慌赎瑰,老刑警劉巖王悍,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異餐曼,居然都是意外死亡压储,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門源譬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來集惋,“玉大人,你說我怎么就攤上這事踩娘」涡蹋” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵养渴,是天一觀的道長雷绢。 經常有香客問我,道長理卑,這世上最難降的妖魔是什么翘紊? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮藐唠,結果婚禮上帆疟,老公的妹妹穿的比我還像新娘。我一直安慰自己宇立,他們只是感情好踪宠,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妈嘹,像睡著了一般柳琢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟋滴,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天染厅,我揣著相機與錄音痘绎,去河邊找鬼津函。 笑死,一個胖子當著我的面吹牛孤页,可吹牛的內容都是我干的尔苦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼允坚!你這毒婦竟也來了魂那?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稠项,失蹤者是張志新(化名)和其女友劉穎涯雅,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體展运,經...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡活逆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了拗胜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔗候。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖埂软,靈堂內的尸體忽然破棺而出锈遥,到底是詐尸還是另有隱情,我是刑警寧澤勘畔,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布所灸,位于F島的核電站,受9級特大地震影響炫七,放射性物質發(fā)生泄漏庆寺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一诉字、第九天 我趴在偏房一處隱蔽的房頂上張望懦尝。 院中可真熱鬧,春花似錦壤圃、人聲如沸陵霉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踊挠。三九已至,卻和暖如春冲杀,著一層夾襖步出監(jiān)牢的瞬間效床,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工权谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剩檀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓旺芽,卻偏偏與公主長得像沪猴,于是被迫代替她去往敵國和親辐啄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理运嗜,服務發(fā)現壶辜,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • /*** 調整窗口的透明度* @param from>=0&&from<=1.0f* @param to>=0&&...
    Dragon_Boat閱讀 967評論 0 1
  • 曾經寫過一篇文章担租,談及PPT90天踐行自己的遺憾砸民,有遺憾是否還接著遺憾嗎?所有的遺憾最后都成為自己心里不敢觸及的一...
    木樨木閱讀 534評論 1 12
  • 今天奋救,一大早來到公司阱洪,早會都沒來得及開就送膠水去公務員小區(qū)客戶家施工,回來之后經理回來開早會菠镇,開完早會就去參加車展...
    鄧承友閱讀 125評論 0 0