urllib2實現簡單爬蟲

王宇昊發表於2014-06-16

網路爬蟲的概念

網路爬蟲(Web Spider)是一種自動獲取網頁內容的程式,是搜尋引擎的重要組成部分。 初次深入瞭解爬蟲的概念是在吳軍博士的《數學之美》中,其中描述網際網路本質上就是一張無形的大網,我們可以把每一個網頁當做網中的節點,超連結作為連線節點的弧。這樣網路爬蟲就可以從任何一個網頁出發,通過圖的遍歷演算法,自動的訪問每一個網頁並將它們存起來。

實際上程式的目的都是代替人來完成大量的重複操作,爬蟲亦是如此。假設現有一個需求是需要獲取中國石油大學新聞網的所有新聞標題及釋出日期來進行統計分析,那麼用人來實現這個需求需要大概以下幾個步驟。

  1. 通過瀏覽器訪問中國石油大學新聞網
  2. 根據網頁內容分析出哪些地方是我們需要獲取的標題和日期。
  3. 將獲取的資料儲存到檔案中。
  4. 跳轉到下一頁重複執行2和3步驟直到所有內容獲取完畢。

接下來我們就會按照以上思路用Python編寫一個簡單的爬蟲。

urllib2抓取網頁內容

網頁抓取就是把URL地址指定的網路資源讀取出來,儲存到本地。相當於我們平時在瀏覽器中通過網址瀏覽網頁,只不過我們看到的是解析過的頁面效果,而程式獲取到的是原始碼等資。 Python的標準庫和元件非常強大,可以處理包括數學計算、網路傳輸、正規表示式等諸多操作。urllib2就是使用各種協議開啟url的一個擴充套件包。最簡單的方法就是呼叫urlopen的方法,比如:

import urllib2

url="http://news.upc.edu.cn/sdyw/"
response=urllib2.urlopen(url)
html=response.read()
print html

在Sublime中輸入以上程式碼,按Ctrl+B執行程式碼(IDLE中F5執行程式碼)可以看到執行結果。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>中國石油大學新聞網</title>
<meta name="keywords" content='石大門戶網,石大資訊,石大新聞,中國石油大學新聞網,創造太陽網,石油之光' />
<!--省略以下執行結果-->

我們在瀏覽器中開啟該網址,右鍵選擇檢視原始碼發現和輸出的內容是一致的,這時我們第一步已經完成了。

正規表示式返回匹配資訊

第二步中分析出哪些資訊是我們需要的資訊這個過程對於人來說非常簡單,但是對於電腦就沒那麼容易了,這裡需要引入正規表示式的概念。

正規表示式,又稱正規表示法、常規表示法(英語:Regular Expression,在程式碼中常簡寫為regex、regexp或RE),電腦科學的一個概念。正規表示式使用單個字串來描述、匹配一系列符合某個句法規則的字串。在很多文字編輯器裡,正規表示式通常被用來檢索、替換那些符合某個模式的文字。

簡而言之,正規表示式就是為了處理相對複雜的文字查詢或替換。 我們在新聞標題上單擊右鍵,選擇檢視元素,這樣Chrome瀏覽器下方就會出現開發者工具,並定位新聞標題所在HTML的位置。 enter image description here

<a href="/sdyw/2014/04/17/15361630123.shtml" target="_blank">1專案入選團中央學校共青團重點工作創新試點</a>

我們分析HTML可以得知<a href="/sdyw/是標題的固定字首,2014/04/17是新聞釋出日期,15361630123.shtml是隨機生成的靜態頁面名稱。1專案入選團中央學校共青團重點工作創新試點是我們需要獲取的新聞標題,最後以</a>結尾,實現程式碼如下。

# -*- coding: utf-8 -*- 
import urllib2
import re
# 1.獲取訪問頁面的HMTL
url="http://news.upc.edu.cn/sdyw/"
response=urllib2.urlopen(url)
html=response.read()
# 2.根據正規表示式抓取特定內容
r=re.compile(r'<a href="/sdyw/(?P<Date>.{10}).*" target="_blank">(?P<Title>.+)</a>')
news=r.findall(html)
print news

在上方加入import re來匯入python自帶的正規表示式模組,通過re.compile(strPattern[, flag])方法將字串形式的正規表示式編譯為Pattern物件,第二個引數flag是匹配模式,我們此處省略填寫。

正規表示式字串開頭有一個字首r,r是raw(原始)的意思。因為在表示字串中有一些轉義符,如表示回車'\n'。如果要表示\需要寫為'\\'。但如果我就是需要表示一個'\'+'n',不用r方式要寫為:'\\n'。但使用r方式則為r'\n'這樣清晰多了。

固定字串<a href="/sdyw/後跟了一個用括號包起來的字串(?P<Date>.{10})(?P<name>...) 是定義一個命名組,(?P=name)則是對命名組的逆向引用。而後面的.匹配任意除換行符"\n"以外的字元。.{10}匹配10個任意字元,<a href="/sdyw/(?P<Date>.{10}).*的意思也就很清楚了,是將/sdyw/後面的10個字元命名為Date,後面的Title也是同樣的道理。

最後通過news=r.findall(html)將在html中所有匹配的字串以列表的形式返回。輸出news就可以看到返回結果了,但是輸出的內容好像存在一些問題,輸出的中文全部為\xe9\xa1\xb9\xe7這種樣式。這是因為存在於list,dict等容器中的字元是以unicode字元編碼方式存在的,單獨列印某一項的時候,會顯示成中文字元。

我們將print news替換成以下程式碼就可以再控制檯看見內容正常輸出了,如果仍然報錯,檢視最上方是否加入了# -*- coding: utf-8 -*-

for i in range(len(news)):
    date=news[i][0]
    title=news[i][1]
    print title+" "+date

至此我們已經完成了第二步獲取標題和日期。由於本文並不是主要介紹正規表示式,建議讀者通過正規表示式30分鐘入門教程來深入瞭解正規表示式。

將獲取的欄位存入MySQL

本節涉及到Python操作MySQL的操作,請讀者在Python IDLE中執行import MySQLdb as mdb來檢查 Python連線MySQL模組是否可用,如果執行報錯請先檢查MySQL和相關模組是否安裝。

如果上面程式碼沒有報錯,那我們可以開始學習如何用Python操作MySQL了。首先,我們新建一個檔案通過Python來建立一個表來熟悉一下MySQL的操作流程(此處有一定基礎可以跳過)。

# -*- coding: utf-8 -*-
import MySQLdb as mdb
import sys

try:
    con=mdb.connect('localhost','user','password','python',charset='utf8')
    cur = con.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTS News\
    (`ID`  INT PRIMARY KEY NOT NULL AUTO_INCREMENT ,`PublishDate`  date NOT NULL ,`Title`  varchar(255) NOT NULL);")
except mdb.Error, e:
    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)
finally:
    if con:    
        con.close()

下面程式碼中,我們連線到名為python的資料庫並執行新建News表的操作。 con=mdb.connect('localhost','user','password','python',charset='utf8') 其中connect()方法一般只需要填寫四個引數。第一個是MySQL資料庫所在的主機地址,在我們的例子中為'localhost';第二個引數是資料庫的使用者名稱,第三個是該使用者的密碼;第四個引數是資料庫的名稱。我們這裡額外填寫了charset='utf8'是為了避免寫入資料庫中出現亂碼,charset是要跟你資料庫的編碼一樣,如果是資料庫是gb2312 ,則寫charset='gb2312'。

備註:寫入資料庫編碼的問題糾結了我很久,請大家一定要注意

cur = con.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTS News\
    (`ID`  INT PRIMARY KEY NOT NULL AUTO_INCREMENT ,`PublishDate`  date NOT NULL ,`Title`  varchar(255) NOT NULL);")

一旦連線成功,我們將會得到一個cursor(遊標)物件,我們通過呼叫該cursor物件的execute()方法來執行SQL語句。

except mdb.Error, e:
    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)

這個地方是用來丟擲異常。

finally:            
    if con:    
        con.close()

最後釋放掉連線資源。

我們可以通過SQL查詢或者客戶端檢視錶是否建立成功。回到正題——如何將獲取的日期和標題存入MySQL中的news表?因為上一節我們已經獲取到資料了,現在只需要呼叫`cusor.excute()執行INSERT INTO news2(PublishDate,Title) VALUES(%s,%s)",(date,title))即可。 最終程式碼如下:

# -*- coding: utf-8 -*- 
import urllib2
import re
import MySQLdb as mdb

url="http://news.upc.edu.cn/sdyw/"
response=urllib2.urlopen(url)
html=response.read()
r=re.compile(r'<a href="/sdyw/(?P<Date>.{10}).*" target="_blank">(?P<Title>.+)</a>')
news=r.findall(html)
con=mdb.connect('localhost','root','root','Python',charset='utf8')
with con:
    curs=con.cursor()
    for i in range(len(news)):
        date=news[i][0]
        title=news[i][3]
        print title+" "+date
        curs.execute("INSERT INTO News(PublishDate,Title) VALUES(%s,%s)",(date,title))

這裡用with關鍵字來代替try-catch,Python會在with結束的時候呼叫con自帶的__exit__方法提交事務和釋放連線資源。同樣,with也可以處理異常。

因為這裡獲取的時間格式不需要處理就直接可以插入到資料庫中,而實際的例子中可能會複雜很多,需要對獲取的資料進行處理,排重等操作。

在資料庫中檢視,我們已經將資料儲存在News表了! enter image description here

遍歷所有新聞頁面

上一節我們獲取到了第一頁的新聞資料,但是我們的目的是需要獲取所有的內容。 那我們跳轉到其他分頁看看有沒有什麼規律可循。通過分析發現,第一頁的url為http://news.upc.edu.cn/sdyw/List_171.shtml,第二頁的url為http://news.upc.edu.cn/sdyw/List_170.shtml,最後一頁的url為http://news.upc.edu.cn/sdyw/List_1.shtml。那麼遍歷所有新聞頁面獲取內容這件事就變的非常簡單了,直接通過for迴圈就可以實現,廢話不說了,直接上程式碼吧。

# -*- coding: utf-8 -*- 
import urllib2
import re
import MySQLdb as mdb

if __name__=="__main__":
    url="http://news.upc.edu.cn/sdyw/List_"
    con=mdb.connect('localhost','root','root','Python',charset='utf8')
    with con:
        for x in xrange(1,171):
            response=urllib2.urlopen(url+str(x)+'.shtml')
            html=response.read()
            r=re.compile(r'<a href="/sdyw/(?P<Date>.{10}).*" target="_blank">(?P<Title>.+)</a>')
            news=r.findall(html)
            curs=con.cursor()
            for i in range(len(news)):
                date=news[i][0]
                title=news[i][5]
                curs.execute("INSERT INTO news2(PublishDate,Title) VALUES(%s,%s)",(date,title))
                print title+" "+date

到此,本文已經結束了,短短的20行程式碼就可以實現一個簡單的爬蟲,這就是Python的魅力。執行import this就可以看到Zen of Python

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than right now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!

相關文章