【Python數據分析】Python3操作Excel-以豆瓣圖書Top250為例


本文利用Python3爬蟲抓取豆瓣圖書Top250,並利用xlwt模塊將其存儲至excel文件,圖片下載到相應目錄。旨在進行更多的爬蟲實踐練習以及模塊學習。

工具

1.Python 3.5

2.BeautifulSoup、xlwt模塊

開始動手

首先查看目標網頁的url: https://book.douban.com/top250?start=0, 然后我嘗試了在代碼里直接通過字符串連接僅改變”start=“后面的數字的方法來遍歷所有的250/25 = 10頁內容,但是后來發現不行,那樣的話出來的永遠是第一頁,於是通過瀏覽器的F12開發者工具檢查,發現start是要post上去的,如下圖:

(圖1)

所以建立一個postData的dict:

postData = {"start": i}    #i為0,25,...225

每次將其post上去即可解決返回都是第一頁的問題。

分析網頁可知,一本書的羅列信息以及要爬取的點如下圖:

(圖2)

從上到下需要爬取的信息有:

1.圖書鏈接地址

2.封面圖片鏈接    我到時候會將此鏈接打開,下載圖片到本地 (download_img函數)

3.書名    要注意的是這里書名取title的內容而不去a標簽中的string信息,因為string信息可能包含諸如空格、換行符之類的字符,給處理造成麻煩,直接取title格式正確且無需額外處理。

4.別名    這里主要是副標題或者是外文名,有的書沒有這項,那么我們就寫入一個“無”,千萬不可以寫入一個空串,否則的話會出現故障,我下面會提到。

5.出版信息  如作者、譯者、出版社、出版年份、價格, 這也是重要信息之一,否則有多本書名字一致可能會無法分辨

6.評分

7.評價人數

除此之外,我還爬取一個“標簽”信息,它在圖書鏈接打開之后的網頁中,找到它的位置如下:

(圖3)

爬到標簽以后將它們用逗號連接起來作為標簽值。

好了,既然明確了要爬的指標,以及了解了網頁結構以及指標所在的html中的位置,那么就可以寫出如下代碼:

    geturl = url + "/start=" + str(i)                     #要獲取的頁面地址
    print("Now to get " + geturl)
    postData = {"start":i}                                #post數據
    res = s.post(url,data = postData,headers = header)    #post
    soup = BeautifulSoup(res.content,"html.parser")       #BeautifulSoup解析
    table = soup.findAll('table',{"width":"100%"})        #找到所有圖書信息的table
    sz = len(table)                                       #sz = 25,每頁列出25篇文章
    for j in range(1,sz+1):                               #j = 1~25
        sp = BeautifulSoup(str(table[j-1]),"html.parser") #解析每本圖書的信息
        #print(sp.div)
        imageurl = sp.img['src']                          #找圖片鏈接
        bookurl = sp.a['href']                            #找圖書鏈接
        bookName = sp.div.a['title']
        nickname = sp.div.span                            #找別名
        if(nickname):                                     #如果有別名則存儲別名否則存’無‘
            nickname = nickname.string.strip() 
        else:
            nickname = "None"
        
        #print(type(imageurl),imageurl)
        #print(type(bookurl),bookurl)
        #print(type(bookName),bookName)
        #print(type(nickname),nickname)
        
        notion = str(sp.find('p',{"class":"pl"}).string)   #抓取出版信息,注意里面的.string還不是真的str類型
        #print(type(notion),notion)
        rating = str(sp.find('span',{"class":"rating_nums"}).string)    #抓取平分數據
        nums = sp.find('span',{"class":"pl"}).string                    #抓取評分人數
        nums = nums.replace('(','').replace(')','').replace('\n','').strip()
        nums = re.findall('(\d+)人評價',nums)[0]
        #print(type(rating),rating)
        #print(type(nums),nums)
        download_img(imageurl,bookName)                     #下載圖片
        book = requests.get(bookurl)                        #打開該圖書的網頁
        sp3 = BeautifulSoup(book.content,"html.parser")     #解析
        taglist = sp3.find_all('a',{"class":"  tag"})       #找標簽信息
        tag = ""
        lis = []
        for tagurl in taglist:
            sp4 = BeautifulSoup(str(tagurl),"html.parser")  #解析每個標簽
            lis.append(str(sp4.a.string))
        
        tag = ','.join(lis)        #加逗號
        if tag == "":              #如果標簽為空,置"無"
            tag = "None"

通過xlwt模塊存入xls文件及其問題

爬取下來了以后當然要考慮存儲,這時我想試試把它存到Excel文件(.xls)中,於是搜得python操作excel可以使用xlwt,xlrd模塊,雖然他們暫時只支持到excel2003,但是足夠了。xlwt為Python生成.xls文件的模塊,而xlrd為讀取的。由於我想的是直接生成xls文件,不需用到xlrd,所以先安裝xlwt。

直接進入Python目錄使用如下命令即可安裝xlwt:

pip3 install xlwt

安裝完后寫出操作代碼,這里同時也寫入txt文件,方便比較:

#建立Excel
workbook = xlwt.Workbook(encoding='utf-8')
sheet = workbook.add_sheet('book_top250',cell_overwrite_ok=True)
item = ['書名','別稱','評分','評價人數','封面','圖書鏈接','出版信息','標簽']
for i in range(1,9):
    sheet.write(0,i,item[i-1])

...
    for j in range(1,sz+1):
        ...
        writelist = [i+j,bookName,nickname,float(rating),int(nums),"I:\\douban\\image\\"+bookName+".jpg",bookurl,notion,tag]
        for k in range(0,9):
            sheet.write(i+j,k,writelist[k])
            txtfile.write(str(writelist[k]))
            txtfile.write('\t')
        txtfile.write(u'\r\n')
        
    workbook.save("I:\\douban\\booktop250.xls")
...

滿以為這樣就可以了,但是還是出現了一些錯誤。

比如曾經出現了寫着寫着就寫不下去了的情況(以下並非以上代碼產生的結果):

(圖4)

這時我把不是str的都改成str了,不該str的盡量用數字(int,float),然后又遇到了下面的情況:

(圖5)

寫到第64項又寫不下去了,但是那些int,float都寫完了,‘無’也是斷斷續續顯示幾個,我想,既然找不到問題,那么慢慢套吧。首先極大可能是中文編碼的問題,因為我把一些可以不為str類型的都賦成非str類型以后都正確地顯示了,而且上圖中的顯示在圖片路徑名那里斷了,所以我讓那一列都不顯示,居然,成功了!

(圖6)

如圖,除了不顯示的那一列,其它完全正常,可以斷定就是下面這里出現的錯誤:

writelist=[i+j,bookName,nickname,float(rating),int(nums),"I:\\douban\\image\\"+bookName+".jpg",bookurl,notion,tag]

我的圖片路徑那里是直接字符串拼接而成的,所以可能會有編碼的錯誤。那么稍微改一下試試:

imgpath = str("I:\\douban\\image\\"+bookName+".jpg");
writelist=[i+j,bookName,nickname,float(rating),int(nums),imgpath,bookurl,notion,tag]

好吧,還是不行,還是出現圖5的問題,但是打印在Python IDLE里面又都是正確的。

既然如此,把圖片鏈接全部改成一樣的英文試一下:

imgpath = str("I:\\douban\\image\\"+"a"+".jpg")
writelist=[i+j,bookName,nickname,float(rating),int(nums),imgpath,bookurl,notion,tag]

 又是正確的:('無'已改為'None')

(圖7)

所以說,還是圖片路徑的問題,那我們索性將圖片路徑那列換成圖片鏈接,采取消極應對方法,反正這項是圖片鏈接還是圖片路徑無關緊要,反正圖片路徑里面有圖片就可以了。此外我還加了一個計時的代碼,計算總爬取時間,因為我覺得這樣干爬太慢了,沒有個將近10分鍾完不成,考慮利用多線程去爬,這里先記錄一下時間以觀后效。然后發現還是不行!現在成了只要imageurl固定(中文也行),就能夠順利輸出到xls中,否則就不行。很詭異。於是我又嘗試了縮短imageurl,實驗得知,當取imageurl[:-6]時是可以的,但imageurl[:-5]就不行了。后面又干脆不寫入imageurl這一列,可以,不寫入別名或者不寫入圖書鏈接都是正常的,但是不寫入標號就不行。至今仍不得解。初步猜測莫非是寫入的字符數受限制了?還得靠更多的實驗才能確定。而且也說不定就是Windows下的編碼問題,這又得靠在Linux下進行實驗判斷。所以要做的事情還很多,這里先把正確的絕大部分工作做了再說。

於是干脆不要圖書地址一列,最后得出如下最終代碼:

# -*- coding:utf-8 -*-
import requests
import re
import xlwt
from bs4 import BeautifulSoup
from datetime import datetime
import codecs

now = datetime.now()             #開始計時
print(now)

txtfile = codecs.open("top250.txt",'w','utf-8')
url = "http://book.douban.com/top250?"

header = { "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.13 Safari/537.36",
           "Referer": "http://book.douban.com/"
           }

image_dir = "I:\\douban\\image\\"
#下載圖片
def download_img(imageurl,imageName = "xxx.jpg"):
    rsp = requests.get(imageurl, stream=True)
    image = rsp.content
    path = image_dir + imageName +'.jpg'
    #print(path)
    with open(path,'wb') as file:
        file.write(image)

#建立Excel
workbook = xlwt.Workbook(encoding='utf-8')
sheet = workbook.add_sheet('book_top250',cell_overwrite_ok=True)

item = ['書名','別稱','評分','評價人數','封面','圖書鏈接','出版信息','標簽']
for i in range(1,9):
    sheet.write(0,i,item[i-1])
        
s = requests.Session()      #建立會話
s.get(url,headers=header)

for i in range(0,250,25):  
    geturl = url + "/start=" + str(i)                     #要獲取的頁面地址
    print("Now to get " + geturl)
    postData = {"start":i}                                #post數據
    res = s.post(url,data = postData,headers = header)    #post
    soup = BeautifulSoup(res.content.decode(),"html.parser")       #BeautifulSoup解析
    table = soup.findAll('table',{"width":"100%"})        #找到所有圖書信息的table
    sz = len(table)                                       #sz = 25,每頁列出25篇文章
    for j in range(1,sz+1):                               #j = 1~25
        sp = BeautifulSoup(str(table[j-1]),"html.parser") #解析每本圖書的信息
        #print(sp.div)
        imageurl = sp.img['src']                          #找圖片鏈接
        bookurl = sp.a['href']                            #找圖書鏈接
        bookName = sp.div.a['title']
        nickname = sp.div.span                            #找別名
        if(nickname):                                     #如果有別名則存儲別名否則存’無‘
            nickname = nickname.string.strip() 
        else:
            nickname = "None"
        
        #print(type(imageurl),imageurl)
        #print(type(bookurl),bookurl)
        #print(type(bookName),bookName)
        #print(type(nickname),nickname)
        
        notion = str(sp.find('p',{"class":"pl"}).string)   #抓取出版信息,注意里面的.string還不是真的str類型
        #print(type(notion),notion)
        rating = str(sp.find('span',{"class":"rating_nums"}).string)    #抓取平分數據
        nums = sp.find('span',{"class":"pl"}).string                    #抓取評分人數
        nums = nums.replace('(','').replace(')','').replace('\n','').strip()
        nums = re.findall('(\d+)人評價',nums)[0]
        #print(type(rating),rating)
        #print(type(nums),nums)
        download_img(imageurl,bookName)                     #下載圖片
        book = requests.get(bookurl)                        #打開該圖書的網頁
        sp3 = BeautifulSoup(book.content,"html.parser")     #解析
        taglist = sp3.find_all('a',{"class":"  tag"})       #找標簽信息
        tag = ""
        lis = []
        for tagurl in taglist:
            sp4 = BeautifulSoup(str(tagurl),"html.parser")  #解析每個標簽
            lis.append(str(sp4.a.string))
        
        tag = ','.join(lis)        #加逗號
        if tag == "":              #如果標簽為空,置"無"
            tag = "None"
        
        writelist=[i+j,bookName,nickname,float(rating),int(nums),imageurl,bookurl,notion,tag]
        for k in range(0,9):
            if(k == 5):
                continue
            sheet.write(i+j,k,writelist[k])
            txtfile.write(str(writelist[k]))
            txtfile.write('\t')
        txtfile.write(u'\r\n')
        
    workbook.save("I:\\douban\\booktop250.xls")

end = datetime.now()    #結束計時
print(end)
print("程序耗時: " + str(end-now))
txtfile.close()
View Code

運行(7分多鍾):

(圖8)

還是斷了,那就真不知道怎么辦才好了。再改變方法,先寫到TXT文本文件再導入到xls中,就先不管本文標題了。

運行:

2016-03-27 21:47:17.914149
Now to get http://book.douban.com/top250?/start=0
Now to get http://book.douban.com/top250?/start=25
Now to get http://book.douban.com/top250?/start=50
Now to get http://book.douban.com/top250?/start=75
Now to get http://book.douban.com/top250?/start=100
Now to get http://book.douban.com/top250?/start=125
Now to get http://book.douban.com/top250?/start=150
Now to get http://book.douban.com/top250?/start=175
Now to get http://book.douban.com/top250?/start=200
Now to get http://book.douban.com/top250?/start=225
2016-03-27 21:56:16.046792
程序耗時: 0:08:58.132643

在.txt中是正確的:

(圖9)

然后在xls文件中選擇數據->導入數據即可得到最終結果:

(圖10)

封面圖片:

(圖11)

問題先解決到這,后面的問題有待深入研究。

后期可改進

1.采用多進程/多線程加快爬取速度

2.可考慮采用xlutis模塊分多步寫入到excel中

3.可考慮改換excel處理模塊

3.考慮在Linux環境下進行試驗

------------------------------------------------------------------------------------------------------

聽人說數據分析絕大部分時間都花在數據采集與清洗,以前不怎么覺得,現在終於有一點感受了,任重道遠啊..

如果您對我的方法有什么看法,歡迎留下您的評論:-)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM