第一個Python程式——部落格自動訪問指令碼

來源:互聯網
上載者:User
動機

今天有朋友寫信說他認為自己的wordpress部落格內顯示的訪問統計資訊不正常,希望我能為他製造一些訪問資訊,供他對比。朋友提出的請求是在短時間內快速開啟100個不同的部落格頁面,以便他從產生的訪問量變化中理解部落格訪問資料。

本人作為一個搞電腦的人,有把任何重複性勞動自動化的衝動,所以雖然點開100個網頁的任務手工做並不複雜,但還是從一開始就徹底否定了。剛好想學Python很久了,於是就拿這次的小機會來學習一把,順便記錄下第一次的Python學習成果。

本文使用Python 2.7.3實現了一個自動訪問部落格的指令碼,涉及以下技術點:

  • 語言基礎

    • 容器(線性表、字典)
    • 邏輯分支、迴圈
    • 控制台格式化輸出
  • HTTP用戶端網路編程
    • 處理HTTP請求
    • 使用HTTPProxy 伺服器
  • PythonRegex
總覽

自動訪問部落格頁面這個操作實際上和網路爬蟲做的事情差不多,基本流程如下:

圖1 部落格自動訪問器工作原理

  1. 給訪問器一個開始位置(例如部落格首頁URL)
  2. 訪問器將URL指向的網頁爬回(爬回網頁這個操作本身相當於在瀏覽器中開啟頁面)
  3. 2中爬回的網頁交給分析器分析。分析器分析後提取出其中的URL加入待訪問URL列表,即URL庫。然後從URL庫中取出下一個要訪問的頁面URL
  4. 迴圈2、3步,直到達到某一終止條件程式退出

剛開始編程時,我們什麼都沒有,只有一個部落格首頁的URL。緊跟著這個URL需要解決的問題就是如何編程爬取URL指向的頁面。爬取到了頁面,才能算是訪問了部落格,也才能獲得頁面的內容,從而提取更多的URL,進行更進一步的爬取。

這樣一來就帶來了如何根據URL擷取頁面資訊的問題,要解決這個問題,需要用到HTTP用戶端編程,這也是接下來一節解決的問題。

urllib2:HTTP用戶端編程

Python中可以實現HTTP用戶端編程的庫有好幾個,例如httplib, urllib, urllib2等。使用urllib2是因為它功能強大,使用簡單,並且可以很容易地使用HTTP代理。

使用urllib2建立一個HTTP串連並擷取頁面非常簡單,只需要3步:

import urllib2
opener = urllib2.build_opener()
file = opener.open(url)
content = file.read()

content即為HTTP請求響應的報文體,即網頁的HTML代碼。如果需要設定代理,在調用build_opener()時傳入一個參數即可:

opener = urllib2.build_opener(urllib2.ProxyHandler({'http': "localhost:8580"}))

ProxyHandler函數接受一個字典類型的參數,其中key為協議名稱,value為host與連接埠號碼。也支援帶驗證的代理,更多細節見官方文檔。

接下來要解決的問題就是從頁面中分離出URL. 這就需要Regex。

Regex

Regex相關的函數位於Python的re模組中,使用前需import re

findall函數返回字串中所有滿足給定正則式的子串:

aelems = re.findall('<a href=".*<\/a>', content)

findall的第一個參數是正則式,第二個參數是字串,傳回值是字串數組,包含content中所有滿足給定正則式的子串。上述代碼返回所有以<a href="開頭,</a>結尾的子串,即所有的<a>標籤。對網頁HTML代碼應用此過濾可擷取所有超連結。如果需要進一步提高過濾的精確度,例如只需要連結指向本部落格(假設當前部落格是http://myblog.wordpress.com),且URL為絕對位址,則可以使用更精確的正則式,例如'<a href="http\:\/\/myblog\.wordpress\.com.*<\/a>'.

擷取到了<a>標籤,就需要進一步提取其中的URL,這裡推薦match函數。match函數的作用是將滿足指定正則式的子串的其中一部分返回。例如對於以下字串(假設存於aelem元素中):

<a href="http://myblog.wordpress.com/rss">RSS Feed</a>

如果需要提取出其中的URL(即http://myblog.wordpress.com/rss),則可以如下的match調用:

matches = re.match('<a href="(.*)"', aelem)

匹配成功時,match返回MatchObject對象,否則返回None. 對於MatchObject,可以使用groups()方法擷取其中包含的所有元素,也可以通過group(下標)擷取,注意group()方法的下標是從1開始的。

以上樣本僅對只含有href一個屬性的<a>元素有效,如果<a>元素中href屬性後還有別的屬性,則按照最長相符原則,上面的match調用會返回不正確的值:

<a href="http://myblog.wordpress.com/rss" alt="RSS Feed - Yet Another WordPress Blog">RSS Feed</a>

將會匹配為:http://myblog.wordpress.com/rss" alt="RSS Feed - Yet Another WordPress Blog

目前對於這種情況還沒有特別好的解決方案,我的做法是先按照空格split一下再匹配。由於href通常都是<a>中第一個出現的屬性,所以可以簡單地如下處理:

splits = aelem.split(' ')
#0號元素為'<a',1號元素為'href="http://myblog.wordpress.com/"'
aelem = splits[1]
#這裡的正則式對應改變
matches = re.match('href="(.*)"', aelem)

當然,這種方法不能保證100%正確。最好的做法應該還是用HTML Parser. 這裡懶得搞了。

提取出URL之後,就要將URL加入URL庫。為了避免重複訪問,需要對URL去重複,這就引出了下一節中字典的使用。

字典

字典,一種儲存key-value對的關聯容器,對應C++裡的stl::hash_map,Java裡的java.util.HashMap以及C#中的Dictionary. 由於key具有唯一性,因此字典可以用來去重。當然,也可以用set,很多set就是將map簡單封裝一下,例如java.util.HashSet和stl::hash_set.

要使用字典構建一個URL庫,首先我們需要考慮一下URL庫需要做什麼:

  1. URL去重:URL從HTML代碼中抽取出來後,如果是已經加入URL庫的URL,則不加入URL庫
  2. 取新URL:從URL庫中取一個還沒訪問過的URL進行下一次爬取

為了同時做到1、2,有以下兩種直觀的做法:

  1. 用一個url字典,其中URL作為key,是否已訪問(True/False)作為value;
  2. 用兩個字典,其中一個用來去重,另一個用來存放還沒訪問過的URL.

這裡簡單起見,用的是第2種方法:

#起始URL
starturl = 'http://myblog.wordpress.com/';
#全部URL,用於URL去重
totalurl[starturl] = True
#未訪問URL,用於維護未訪問URL列表
unusedurl[starturl] = True

#中間省略若干代碼

#取下一個未用的URL
nexturl = unusedurl.keys()[0];
#將該URL從unusedurl中刪除
del unusedurl[nexturl]
#擷取頁面內容
content = get_file_content(nexturl)
#抽取頁面中的URL
urllist = extract_url(content)
#對於抽取出的每個URL
for url in urllist:
#如果該URL不存在於totalurl中
if not totalurl.has_key(url):
#那麼它一定是不重複的,將其加入totalurl中
totalurl[url] = True
#並且加入為訪問列表中
unusedurl[url] = True
結束

最後貼上完整的代碼:

import urllib2
import time
import re

totalurl = {}
unusedurl = {}

#產生ProxyHandler對象
def get_proxy():
return urllib2.ProxyHandler({'http': "localhost:8580"})

#產生指向代理的url_opener
def get_proxy_http_opener(proxy):
return urllib2.build_opener(proxy)

#擷取指定URL指向的網頁,調用了前兩個函數
def get_file_content(url):
opener = get_proxy_http_opener(get_proxy())
content = opener.open(url).read()
opener.close()
#為方便正則匹配,將其中的分行符號消掉
return content.replace('\r', '').replace('\n', '')

#根據網頁HTML代碼抽取網頁標題
def extract_title(content):
titleelem = re.findall('<title>.*<\/title>', content)[0]
return re.match('<title>(.*)<\/title>', titleelem).group(1).strip()

#根據網頁HTML代碼抽取所有<a>標籤中的URL
def extract_url(content):
urllist = []
aelems = re.findall('<a href=".*?<\/a>', content)
for aelem in aelems:
splits = aelem.split(' ')
if len(splits) != 1:
aelem = splits[1]
##print aelem
matches = re.match('href="(.*)"', aelem)
if matches is not None:
url = matches.group(1)
if re.match('http:\/\/myblog\.wordpress\.com.*', url) is not None:
urllist.append(url)
return urllist

#擷取字串格式的時間
def get_localtime():
return time.strftime("%H:%M:%S", time.localtime())

#主函數
def begin_access():

starturl = 'http://myblog.wordpress.com/';
totalurl[starturl] = True
unusedurl[starturl] = True
print 'seq\ttime\ttitle\turl'

i = 0
while i < 150:

nexturl = unusedurl.keys()[0];
del unusedurl[nexturl]
content = get_file_content(nexturl)

title = extract_title(content)
urllist = extract_url(content)

for url in urllist:
if not totalurl.has_key(url):
totalurl[url] = True
unusedurl[url] = True

print '%d\t%s\t%s\t%s' %(i, get_localtime(), title, nexturl)

i = i + 1
time.sleep(2)

#調用主函數
begin_access()
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.