[心得] Python爬蟲教學(2)

這一篇是延續上一篇的內容

But...在程式結構上做了調整

加入物件導向的基礎(這是最最基本中的基本...再複雜一點我也不會)

如果看到這篇的初學者一時無法從第一篇轉到這篇來

可以先參考一下 https://pastebin.com/DwQF0GHx

上面的連結是我把第一篇的程式改成物件導向的寫法

程式的流程都沒變動(應該啦)

 

接下來就進入本篇實作的重點:

如何將爬蟲的數據存入資料庫!

這個系列的爬蟲程式,是抓取妹子圖的圖片

我們所要記錄的內容也很簡單

1.每個套圖的標題名稱

2.每張圖片的原始連結

本範例是使用sqlite3當作儲存的資料庫

資料庫的欄位規劃如下圖:

sqlite3

資料庫裡會有兩張資料表Album、Images

Images的album_id設定為Foreign Key,參考對象為Album資料表中的id

在這先說明一下,這個範例是不需要用到兩張資料表

資料可以都在一張資料表

但是關聯式的資料表,以後會用到。

接下來就直接上程式碼了:

'''
author:smilehsu
blog:smilehsu.cc
requirements:Windows7、python3.52
date:2017/02/14
程式用物件化改寫

2017/02/22 
加入 sqlite

'''
import os, requests, shutil
import sqlite3 as lite
from bs4 import BeautifulSoup
 
#base_url='http://meizitu.com/a/'
fk=1
all_link=[]
error_page=[]
dir_path='d:\meizitu'
sqlite_path='d:\meizitu\meizituDB.sqlite'
 
#sql語法
#如果資料庫已經album資料表就刪掉它
sql1="DROP TABLE IF EXISTS 'album';"
#建立新的 album資料表
sql2="CREATE TABLE 'album' ('id' INTEGER PRIMARY KEY  NOT NULL , 'title' VARCHAR);"
#如果資料庫已經album_images資料表就刪掉它
sql3="DROP TABLE IF EXISTS 'album_imags';"
#建立新的 album_images資料表
#FOREIGN KEY(album_id) REFERENCES album(id) 設定 album_id為Foreign Key,跟album資料表中的id連結
sql4="CREATE TABLE 'album_imags' ('img_id' INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL , 'album_id' INTEGER NOT NULL ,\
'title' VARCHAR NOT NULL , 'img_src' VARCHAR NOT NULL ,FOREIGN KEY(album_id) REFERENCES album(id) );"
 
#資料庫連線,如果路徑底下沒有meizituDB.sqlite則會自動建立
conn= lite.connect(sqlite_path)
 
#在爬蟲程式開始運作前,先建立資料庫
cur=conn.cursor()
cur.execute(sql1)
cur.execute(sql2)
cur.execute(sql3)
cur.execute(sql4)
conn.commit()
conn.close()
 
class meizitu():
 
    def all_url(self,url,maxpage):
        for i in range(1,maxpage+1):
            page_url=url+str(i)+'.html'
            all_link.append(page_url)
       
        #計數器
        counter=1
        for p in all_link:
            html=self.request(p)
            soup=BeautifulSoup(html.text,'lxml')
           
            try:
                #取得頁面的title跟該頁面的圖片連結
                title=soup.find('div',{'class':'metaRight'}).find('a')
                #取得圖片連結
                img_url=soup.find('div',{'class':'postContent'}).find_all('img')
               
                #測試用 印出頁面的title
                #print(title.text)
               
                #測試用
                #print(len(img_url),img_url)
               
                #要存圖片的資料夾檔名就用頁面的title
                dirname=title.text
               
                #寫入資料庫
                album_sql="insert or ignore into album values({},'{}');".format(counter,dirname)
                #測試sql語法
                #print('insert_sql=',album_sql)
                             
                conn= lite.connect(sqlite_path)
                cur=conn.cursor()
                cur.execute(album_sql)
                conn.commit()
                conn.close()
               
                #建立資料夾
                self.mkdir(dirname)
               
                fk=counter
                #check fk value in main()
                #print('main()裡的fk值',fk)
               
                #儲存圖檔
                self.save(img_url,dirname,fk)
               
                counter+=1
               
            except Exception as e:
                print('error: {}'.format(e))
                error_page.append(p)
                pass
           
    def request(self,url):
        headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}
        res = requests.get(url, headers=headers,stream=True)
        res.encoding='gb2312'
        return res
   
    def mkdir(self, dirname):
        dirname=dirname.strip()
        DisExists = os.path.exists(os.path.join(dir_path, dirname))
        mydir_path=os.path.join(dir_path, dirname)
 
       
        if DisExists==0:
            print('建立資料夾:'+mydir_path)
            os.makedirs(mydir_path)
            os.chdir(mydir_path)
            return True
 
        else:
            print('資料夾已存在'+mydir_path)
            os.chdir(mydir_path)
            return False
   
    def save(self, img_url,dirname,fk):
        #check fk value in save()
        #print('save()裡的 fk值=',fk)
       
        for pic in img_url:
            #路徑check
            #print('目前工作目錄:'+os.getcwd())
           
            #頁面裡的圖片連結
            pic_src=pic['src']
            #測試用
            #print('要下載的圖檔連結'+pic_src)
           
            #下載圖片後要存檔的檔名
            pic_name=pic_src.split('/')[-1]
                     
            #寫入資料庫
            img_sql="INSERT INTO album_imags (album_id,title,img_src) VALUES ({},'{}','{}');".format(fk,dirname,pic_src)
            #check sql語法
            #print('table img sql=',img_sql)
            #寫入資料庫
            conn= lite.connect(sqlite_path)
            cur=conn.cursor()
            cur.execute(img_sql)
            conn.commit()
            conn.close()
           
            #下載圖片後要存檔的檔名
            #檢查檔案是否已經存存在
            #存檔的名稱與下載的圖檔名稱一樣
            #所以可以判斷是否已經下載過
            FisExists = os.path.exists(pic_name)
            if FisExists ==1:
                print('檔案{}已存在'.format(pic_name))
            else:
                #下載圖片
                print('開始下載{}'.format(pic_name))
               
                #先停用下載功能
                #get_pic=self.request(pic_src)
                #f=open(pic_name,'wb')
                #shutil.copyfileobj(get_pic.raw,f)
                #f.close()
                #del get_pic
 
Meizitu=meizitu()
Meizitu.all_url(url='http://meizitu.com/a/',maxpage=50)

最後程式有順利執行的話

資料庫應該會有資料寫進去

大概會長這樣:

sqlite

說明一下Foreign Key(簡稱FK)

這個範例是相簿<->相片

一個相簿裡會有N張相片

FK的用處就是讓每一張相片找到相對應的相簿

就如同讓小甜甜的相片不會跑到林志玲相簿裡!!!

下面是用Firefox的套件 Sqlite Manager的畫面

將不正確的FK寫入相簿裡會出現錯誤的情況

(album資料表裡沒有6,偏要寫進去)

(因為有設FK,所以不准寫入)(小甜甜的照片不准放到林志玲的相簿裡!)

本次教學內容就到這邊結束了,謝謝收看


參考資料:

1.SQLite Foreign Key Support

2.小白爬蟲第三彈之去重去重

後續:

這個範例就是[致敬]參考資料2的內容

原本的內容是用MangoDB

我改為Sqlite

但我也完整的[致敬]一份MongoDB版本

可參考[這裡]

 

這個程式基本上可以運作無誤

但目前已知BUG是

如果程式中斷再執行

那FK的值就會產生錯誤

FK的值最理想的狀態是

去抓album的id值而不是用迴圈去跑

解決方案:

另外如何取得最新一筆PK值:

How to retrieve inserted id after inserting row in SQLite using Python?

=> cursor.lastrowid