1. 程式人生 > 其它 >Python]新手寫爬蟲全過程(已完成)

Python]新手寫爬蟲全過程(已完成)

今天早上起來,第一件事情就是理一理今天該做的事情,瞬間get到任務,寫一個只用python字串內建函式的爬蟲,定義為v1.0,開發中的版本號定義為v0.x。資料存放?這個是一個練手的玩具,就寫在txt文本里吧。其實主要的不是學習爬蟲,而是依照這個需求鍛鍊下自己的程式設計能力,最重要的是要有一個清晰的思路(我在以這個目標努力著)。ok,主旨已經訂好了,開始‘擼串’了。

目標網站:http://bohaishibei.com/post/category/main/(一個很有趣的網站,一段話配一個圖,老有意思了~)網站形式如下:

目標:把大的目標分為幾個小的目標。因為第一次幹這個,所以對自己能力很清楚,所以完成順序由簡單到複雜。

1.爬取一期的內容,包括標題,和圖片的url

   2.把資料存在本地的txt檔案中

3.想爬多少就爬就爬少

4.寫一個網站,展示一下。(純用於學習)

Let‘s 搞定它!

時間——9:14

  把昨天晚上做的事情交代一下。昨天晚上寫的程式碼實現了爬取一期裡的所有標題。

第一步:

我用的是google瀏覽器,進入開發者模式,使用’頁面內的元素選擇器‘,先看一下內頁中的結構,找到我們要的資料所在’標籤‘

這裡我們需要的博海拾貝一期的內容全部在<article class="article-content">這個標籤裡面,如下圖:

第一條紅線是:頁面內的元素選擇器

第二條是:內容所在標籤

第三條是:title

經過分析得出,我只要<article class="article-content">,這個標籤的內容:所以寫了下面的方法:

def content(html):    # 內容分割的標籤
    str = '<article class="article-content">'
    content = html.partition(str)[2]
    str1 = '<div class="article-social">'
    content = content.partition(str1)[0]    return content # 得到網頁的內容

這裡需要說一下:在寫這個爬蟲之前我就打算只用字串的內建函式來處理匹配問題,所以我就上http://www.w3cschool.cc/python/進入到字串頁面,大致看了一遍字串的內建函式有哪些。

partition() 方法用來根據指定的分隔符將字串進行分割。

如果字串包含指定的分隔符,則返回一個3元的元組,第一個為分隔符左邊的子串,第二個為分隔符本身,第三個為分隔符右邊的子串。

partition() 方法是在2.5版中新增的。參考:http://www.w3cschool.cc/python/att-string-partition.html

這樣我就得到只有內容的字串了,乾淨~

第二步:

得到title的內容。title的格式如下,我只要’【2】‘後面的文字,後面的img暫時不考慮一步步的來。

<p>【2】這是我最近的狀態,請告訴我不是我一個人!</p><p><img src=http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8ifthnj30an0aot8w.jpg /></p><p>

我寫了下面的方法:

def title(content,beg = 0):    # 思路是利用str.index()和序列的切片
    try:
        title_list = []        while True:   
            num1 = content.index('】',beg)
            num2 = content.index('</p>',num1)
            title_list.append(content[num1:num2])
            beg = num2        
    except ValueError:         return title_list

這裡用try....except是因為我不知道怎麼跳出迴圈。。。。求大神有更好的方法告訴我。

我這裡跳出迴圈用的是當丟擲VlaueError異常就說明找不到了,那就返回列表。就跳出迴圈了。

num1是】的位置,num2是</p>的位置,然後用序列的切片,咔嚓咔嚓一下就是我想要的資料了。這裡需要注意的是:切片’要頭不要尾‘所以我們的得到的資料就是這個樣子的:

哎呀,這個是什麼鬼!要頭不要尾就是這個意思!

然後我就想:那就把num1加1不就完了嗎?我真是太天真了。。。。

請+3,我覺得原理是這樣的,這個是個中文字元!(求大神指點)

第三步:

交代清楚我昨天晚上做的事情了,記錄下時間——10:01,下面我要爬圖片的url了。這裡要說一下,如果要把圖片下下來,最重要的一步就是得到url,然後下載下來儲存到本地(用文字的IO)。

我先獲得url,實現原理同獲取title,我在想,既然一樣解除安裝獲取title的方法裡好,還是在寫一個方法好。我單獨寫了一個方法,但是其實就是複製了一下title的方法,改了下匹配的字串,程式碼如下:

def img(content,beg = 0):    # 思路是利用str.index()和序列的切片
    try:
        img_list = []        while True:   
            src1 = content.index('http',beg)
            src2 = content.index('/></p>',src1)
            img_list.append(content[src1:src2])
            beg = src2        
    except ValueError:         return img_list

結果圖如下:

這裡發現,有的時候一個title會有很多個圖片。我思考之後有如下思路:

1.需要寫一個方法,當一個title出現多個圖片的時候,捕獲url。這個需要有一個判斷語句,當url長度大於一個url長度的時候,才需要呼叫這個函式。

2.多個圖片的url怎麼放?使用符號隔開存放還是巢狀放入一個數組裡面?我這裡打算用’|‘隔開,這樣的話加一個判語句,或者先判斷一下url長度,都可以進行。

這個問題先放在這裡,因為當我要下載的時候這個url才需要過濾,所以先進行下一步,把資料存到本地txt文中,這裡在解決這個問題也不晚。

第四步:

把資料存到本地的txt中。Python檔案IO參考資料:http://www.w3cschool.cc/python/python-files-io.html

這裡需要注意的是,文字寫入的時候記得close,還有就是注意開啟文字的模式。

時間——11:05 吃個飯先

時間——11:44 回來了

這裡我考慮了一個問題,根據《編寫高質量程式碼——改善python程式的91個建議》這本書中寫道的,字串連線時,用jion()效率高於’+‘

所以我寫了如下程式碼:

def data_out(data):    #這裡寫成一個方法好處是,在寫入文字的時候就在這裡寫
    fo = open("/home/qq/data.txt", "a+") #這裡注意重新寫一個地址    #for i,e in enumerate(data):
    fo.write("n".join(data)); 
        #print '第%d個,title:%s' % (i,e)
    # 關閉開啟的檔案
    fo.close()

這樣造成了一個問題,看圖

造成最後一個和新的一個列表寫入時在同一行。同時用with....as更好。修改後程式碼如下:

def data_out(data):    #寫入文字
    with open("/home/qq/foo.txt", "a+") as fo:
        fo.write('n')
        fo.write("n".join(data)); 

下面研究title和img以什麼樣的格式存入txt文字:

title$img

這裡我有一個概念混淆了,+和join()方法的效率問題主要在連線多個字串的時候,我這個只用連線一次,不需要考慮這個問題。

def data_out(title, img):    #寫入文字
    with open("/home/qq/foo.txt", "a+") as fo:
        fo.write('n')
        size = 0
        for size in range(0, len(title)):       
            fo.write(title[size]+'$'+img[size]+'n'); 

文字中的內容如下:

願你貪吃不胖,願你懶惰不醜,願你深情不被辜負。$http://ww1.sinaimg.cn/mw690/005CfBldtw1etay8dl1bsj30c50cbq4m.jpg" 這是我最近的狀態,請告訴我不是我一個人!$http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8ifthnj30an0aot8w.jpg 
引誘別人和你擊拳慶祝,然後偷偷把手勢變成二,就可以合體成為蝸牛cosplay……$http://ww2.sinaimg.cn/mw690/005CfBldtw1etay8fzm1sg30b40644qq.gif 
原來蝸牛是醬紫吃東西的。。。。漲姿勢!$http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8egg8vg30bo08ax6p.gif 

寫入文字的最後,解決多個圖片的問題:

def many_img(data,beg = 0):    #用於匹配多圖中的url
    try:
        many_img_str = ''
        while True:
            src1 = data.index('http',beg)
            src2 = data.index(' /><br /> <img src=',src1)
            many_img_str += data[src1:src2]+'|' # 多個圖片的url用"|"隔開
            beg = src2    except ValueError:        return many_img_str              
         
def data_out(title, img):    #寫入文字
    with open("/home/qq/data.txt", "a+") as fo:
        fo.write('n')        for size in range(0, len(title)):            # 判斷img[size]中存在的是不是一個url
            if len(img[size]) > 70: 
                img[size] = many_img(img[size])# 呼叫many_img()方法
            fo.write(title[size]+'$'+img[size]+'n')        

輸出如下:

元氣少女陳意涵 by @TopFashionStyle$http://ww2.sinaimg.cn/mw690/005CfBldtw1etay848iktj30bz0bcq4x.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay83kv5pj30c10bkjsr.jpg|http://ww3.sinaimg.cn/mw690/005CfBldtw1etay82qdvsj30c10bkq3z.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay836z8lj30c00biq40.jpg|http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8279qmj30ac0a0q3p.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay81ug5kj30c50bnta6.jpg|http://ww2.sinaimg.cn/mw690/005CfBldtw1etay8161ncj30c20bgmyt.jpg|http://ww2.sinaimg.cn/mw690/005CfBldtw1etay804oy7j30bs0bgt9r.jpg|

暫時功能是實現了,後面遇到問題需要修改在改吧。。。。新手走一步看一步!!!

到此為止,已經完成了前兩個簡單的計劃:    1.爬取一期的內容,包括標題,和圖片的url    2.把資料存在本地的txt檔案中

全部程式碼如下:

#coding:utf-8import urllib#######爬蟲v0.1 利用urlib 和 字串內建函式######def getHtml(url):    # 獲取網頁內容
    page = urllib.urlopen(url)
    html = page.read()    return htmldef content(html):    # 內容分割的標籤
    str = '<article class="article-content">'
    content = html.partition(str)[2]
    str1 = '<div class="article-social">'
    content = content.partition(str1)[0]    return content # 得到網頁的內容
    def title(content,beg = 0):    # 匹配title
    # 思路是利用str.index()和序列的切片
    try:
        title_list = []        while True:   
            num1 = content.index('】',beg)+3
            num2 = content.index('</p>',num1)
            title_list.append(content[num1:num2])
            beg = num2        
    except ValueError:         return title_list         
def get_img(content,beg = 0):    # 匹配圖片的url
    # 思路是利用str.index()和序列的切片
    try:
        img_list = []        while True:   
            src1 = content.index('http',beg)
            src2 = content.index('/></p>',src1)
            img_list.append(content[src1:src2])
            beg = src2        
    except ValueError:         return img_listdef many_img(data,beg = 0):    #用於匹配多圖中的url
    try:
        many_img_str = ''
        while True:
            src1 = data.index('http',beg)
            src2 = data.index(' /><br /> <img src=',src1)
            many_img_str += data[src1:src2]+'|' # 多個圖片的url用"|"隔開
            beg = src2    except ValueError:        return many_img_str              
         
def data_out(title, img):    #寫入文字
    with open("/home/qq/data.txt", "a+") as fo:
        fo.write('n')        for size in range(0, len(title)):            # 判斷img[size]中存在的是不是一個url
            if len(img[size]) > 70: 
                img[size] = many_img(img[size])# 呼叫many_img()方法
            fo.write(title[size]+'$'+img[size]+'n')
        
   
                   
content = content(getHtml("http://bohaishibei.com/post/10475/"))
title = title(content)
img = get_img(content)
data_out(title, img)# 實現了爬的單個頁面的title和img的url並存入文字

時間——15:14

下面要重新分析網站,我已經可以獲得一期的內容了,我現在要得到,其它期的url,這樣就想爬多少就爬多少了。

目標網址:http://bohaishibei.com/post/category/main/

按照上面的方法進入開發者模式分析網站結構,找出目標資料所在的標籤,擼它!

在首頁中需要的資料全部都在<div class="content">標籤裡,分隔方法如下:

def main_content(html):# 首頁內容分割的標籤
    str = '<div class="content">'
    content = html.partition(str)[2]
    str1 = '</div>'
    content = content.partition(str1)[0]    return content # 得到網頁的內容

我暫時需要的資料:每一期的名字和每一期的url。

經過我的分析:該網站的每期的url格式是這樣的:"http://bohaishibei.com/post/10189/"只有數字是變化的。

後來我又發現,我想要的這兩個資料都在<h2>這個標籤下面,獲取每期url的方法如下:

def page_url(content, beg = 0):    try:
        url = []        while True:
            url1 = content.index('<h2><a href="',beg)+13
            url2 = content.index('" ',url1)
            url.append(content[url1:url2]) 
            beg = url2    except ValueError:        return url   

title的格式,

我思考了一下,我要title其實沒什麼太大的意思,使用者有不可能說我要看那期,只需要輸入看多少期就可以了,標題沒有什麼實際意義(不像內容中的title是幫助理解改張圖笑點的)。所以我打算在這個版本中只實現,你輸入想檢視要多少期,就返回多少期!

那麼下面就需要一個策略了:

http://bohaishibei.com/post/category/main/ 共20期

http://bohaishibei.com/post/category/main/page/2/ 共20期

......

經檢視,每頁都是20期

當你要檢視的期數,超過20期的時候需要,增加page的數值,進入下一頁進行獲取

最後一頁為這個:http://bohaishibei.com/post/category/main/page/48/

實現程式碼,這個我要想一想怎麼寫,我是第一次寫爬蟲,不要嘲諷我啊!

時間——17:09

感覺快實現了,還在寫:

def get_order(num):
    page = num / 20
    order = num % 20 # 超出一整頁的條目
    for i in range(1, page+1): # 需這裡需要尾巴
        url = 'http://bohaishibei.com/post/category/main/page/%d' % i 
        print url        
        if (i == page)&(order > 0):
            url = 'http://bohaishibei.com/post/category/main/page/%d' % (i+1) 
            print url+",%d條" % order
get_order(55) 

執行結果:

http://bohaishibei.com/post/category/main/page/1http://bohaishibei.com/post/category/main/page/2http://bohaishibei.com/post/category/main/page/3,15條2
~~~~~~~~~~~~
15

這裡我考慮是這樣的我需要重寫 page_url,需要多加一個引數,如下:

# 新增一個引數order,預設為20def page_url(content, order = 20, beg = 0):    try:
        url = []
        i = 0        while i < order:
            url1 = content.index('<h2><a href="',beg)+13
            url2 = content.index('" ',url1)
            url.append(content[url1:url2])
            beg = url2
            i = i + 1        return url    except ValueError:        return url   

下面這個方法是傳入引數num(需要多少期),一頁20期,返回每一期的url,程式碼如下:

def get_order(num):# num代表獲取的條目數量
    url_list = []
    page = num / 20 
    order = num % 20 # 超出一整頁的條目
    if num < 20:  # 如果獲取的條目數量少於20(一頁20個),直接爬取第一頁的num條
        url = 'http://bohaishibei.com/post/category/main'
        main_html = getHtml(url)
        clean_content = main_content(main_html)         
        url_list = url_list + page_url(clean_content, num)  
    for i in range(1, page+1): # 需這裡需要尾巴
        url = 'http://bohaishibei.com/post/category/main/page/%d' % i # 爬取整頁的條目
        main_html = getHtml(url)
        clean_content = main_content(main_html)
        url_list = url_list + page_url(clean_content) #獲取整夜
      
        if (i == page)&(order > 0):  # 爬到最後一頁,如果有超出一頁的條目則繼續怕order條
            url = 'http://bohaishibei.com/post/category/main/page/%d' % (i+1) 
            main_html = getHtml(url)
            clean_content = main_content(main_html)         
            url_list = url_list + page_url(clean_content, order)            
            #print len(page_url(clean_content, order))
    return url_list

下面開始gogogo

order = get_order(21)for i in range(0, len(order)): #這個遍歷列表太醜了,改了: for i in order
    html = getHtml(order[i])     
    content_data = content(html)
    title_data = title(content_data)
    img_data = get_img(content_data)
    data_out(title_data, img_data)

ok了所有的程式碼都寫完了

完整的程式碼我已經上傳到我的github上了,地址為:https://github.com/521xueweihan/PySpider/blob/master/Spider.py

這裡我在測試的時候有bug,因為該網站上有時候有的地方沒有img的地址。如下圖

我的程式碼也就跟著出問題了,因為我的title和img列表數量不一了,而列表長度我是以title的len()為準,結果就出現超出範圍了。

這裡記錄一下,然後我要去除bug了。

ok啦,bug消除了。我改了img的匹配方法如下:

def get_img(content,beg = 0):    # 匹配圖片的url
    # 思路是利用str.index()和序列的切片
    try:
        img_list = []        while True:   
            src1 = content.index('src=',beg)+4  # 這樣寫就可以匹配src="/"
            src2 = content.index('/></p>',src1)
            img_list.append(content[src1:src2])
            beg = src2        
    except ValueError:         return img_list

主函式:

order = get_order(30) # get_order方法接受引數,抓取多少期的資料for i in order:  # 遍歷列表的方法
    html = getHtml(i)            
    content_data = content(html)
    title_data = title(content_data)
    img_data = get_img(content_data)
    data_out(title_data, img_data)

爬下來的資料:

data.txt屬性(共30期的資料):

終於寫完了!

開始時間——9:14

寫爬蟲,吃飯,洗澡,休息了一會。

結束時間——21:02

呼,沒有半途而廢就知足了,感覺這樣把寫爬蟲的流程走了一遍下次再寫的話會快一些吧。

總結:

整個過程,純手寫,沒有參考別人的程式碼。這一點可以贊一下。

這次寫爬蟲就是強制自己不用正則表示式,和XPATH,發現有很多地方,用這兩個會很方便。這讓我下定決心去學正則表示式和Xpath,哈哈。體會過才有深有感觸。

下一個目標是學習正則表示式和Xpath。一點點來,當我學完就來寫爬蟲v2.0,逐步完善吧,如果上來就要寫難得,我的智商著急啊!

然後多看看別人的爬蟲,學習別人厲害的地方,提高自己。