一個簡單的爬蟲

功能:爬取目標網站全部主要圖片(例子中是美圖錄網站的全部寫真圖片,按人名分類)

本示例使用Python3.5,需要額外安裝BeautifulSoup 4

BeautifulSoup 4 安裝方法:

Linux:
sudo apt-get install python-bs4

Mac:
sudo easy_install pip
pip install beautifulsoup4

Windows:
下載源碼后,
python setup.py install
或者:
pip install beautifulsoup4

具體安裝方式見:<a >點這里</a>

分析網站結構

目標網站<a >“美圖錄”</a>(別問我為什么選這個網站。。。百度上“隨便”找的)

因為打算下載全部的網頁圖片,所以從最小的單元開始,也就是圖片集(再小就是單一的圖片了,也就可以直接下載了)

先打開首頁,隨便點開一個圖片集,發現圖片集的地址是這樣的
http://www.meitulu.com/item/7487.html
在圖片集中檢查頁面元素,如下所示

<div class="content">
  <center>
    ![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第1張圖](http://upload-images.jianshu.io/upload_images/2475481-8cdfce535296ab31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  </center>
  <center>
    ![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第3張圖](http://upload-images.jianshu.io/upload_images/2475481-43e882deb2d0c0cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  </center>
  <center>
    ![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第4張圖](http://upload-images.jianshu.io/upload_images/2475481-2d5cb01257ad639e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)    
  </center>
  <center>
    ![[Ugirls尤果網] U181 陳雅漫 寫真套圖_第1頁/第5張圖](http://upload-images.jianshu.io/upload_images/2475481-8e773c5ec770c466.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  </center>
</div>

發現每一張主要圖片資源鏈接都在center標簽中,這樣就可以在這個頁面上提取圖片鏈接并下載了
繼續向下,發現下圖所示,圖片并不是存放在一個頁面中的


頁面列表

而檢查這里的html代碼可以看到

<div id="pages">
  <a class="a1" >上一頁</a>
  <span>1</span>
  <a >2</a>
  <a >4</a>
  <a >5</a>
  <a >6</a>
  <a >7</a>
  <a >8</a>
  <a >9</a>
  <a >10</a>
  ".."
  <a >16</a>
  <a class="a1" >下一頁</a>
</div>

這個頁面列表在class="pages"的div標簽中,當前頁是用span標簽裝飾的,我們可以通過提取 下一頁 按鈕的鏈接來繼續下載下一個頁面的圖片,但是我們怎么知道什么時候會到最后一頁呢?點擊最后一個頁面的按鈕,這里就是16頁。再次檢查這一部分的html代碼

<div id="pages">
  <a class="a1" >上一頁</a>
  <a >1</a>
  ".."
  <a >7</a>
  <a >8</a>
  <a >9</a>
  <a >10</a>
  <a >11</a>
  <a >12</a>
  <a >13</a>
  <a >14</a>
  <a >15</a>
  <span>16</span>
  <a class="a1" >下一頁</a>
</div>

從這段代碼中可以看到,下一頁 按鈕的鏈接指向的是16頁,也就是當前頁,而前面的頁面指向的都是當前頁的下一頁。所以我們可以利用這一點來判斷是否到最后一頁。這樣我們就有了下載一個完整圖片集的思路了。

下面我們看看如何獲得所有圖片集的鏈接

圖集分類

發現網站首頁有一個圖集分類,我們可以認為他把網站上所有的資源都放在這里分好類了,隨便點開一個分類,可以看到里面有排列整齊的圖集,檢查html代碼

<div class="boxs">
  <ul class="img">
    <li>
      <a  target="_blank">
        ![[尤蜜薈] 可樂Vicky 蘇梅島旅拍 第二刊 ~[43]](http://upload-images.jianshu.io/upload_images/2475481-1915763ae66ee8ad.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
      </a>
      <p><span>3</span>圖片: 43 張(1600X2400)</p>
      <p>機構:
        <a  target="_blank" class="tags">推女神</a>
      </p>
      <p>模特:
        <a  target="_blank" class="tags">可樂Vicky</a>
      </p>
      <p>標簽:
        <a  target="_blank" class="tags">極品</a>
        <a  target="_blank" class="tags">女神</a>
        <a  target="_blank" class="tags">清新</a>
        <a  target="_blank" class="tags">清純</a>
        <a  target="_blank" class="tags">唯美</a>
        <a  target="_blank" class="tags">戶外</a>
        <a  target="_blank" class="tags">養眼</a>
      </p>
      <p class="p_title">
        <a  target="_blank">[尤蜜薈] 可樂Vicky 蘇梅島旅拍 第二刊 ~</a>
      </p>
    </li>
    "..."
    <!--為了方便查看,這里省略了一堆li標簽-->
  </ul>
</div>

從這段代碼中可以發現,所有的圖集被放在了<div class="boxs">標簽中,每個圖集的信息中包含模特名字,發行機構和一系列的標簽。每一個信息對應一個鏈接,鏈接中是包含對應信息的圖集的分類頁面,這里我們按照人名分類,所以只要檢索 模特 關鍵字就可以了。
這段頁面的下方也是一個頁面列表,檢查html元素會發現與圖集的列表模式相同。

另外,對于某些只有一個圖集的人來說,他沒有對應的分類頁面,對于這些人要另外處理

小結

根據這些特征,我們遍歷分類頁面中的所有圖集,通過字典記錄人名對應的鏈接,如果遇到沒有分類頁面的人,則直接創建文件夾,下載圖集。這樣我們前期的分析工作就完成了,下面

開始寫爬蟲吧

先初始化幾個要用到的全局變量

categaries = {}                        # 分類列表
person = {}                            # 人名列表
PATH = os.getcwd()                     # 根目錄路徑
forbidchar = r'<|>|/|\\|\||:|"|\*|\?'  # 系統禁止用作文件名的字符,正則表達式

一、圖片下載函數

首先,我們要下載網站上所有的圖片,所以需要有一個給定圖片鏈接就能下載下來的函數:

def downloadimg(link, name):            # link為圖片鏈接,name為圖片名字
    data = urlopen(link, timeout=10)    # 打開連接
    tname = name+".jpg"                 # 給圖片命名
    with open(tname, "ab") as code:     # 以追加二進制模式打開文件,并保存數據
        code.write(data.read())
    print(tname+" is done.")            # 打印提示文字

但這還不夠,因為經常會碰到鏈接沒有響應的情況,所以加上異常處理

def downloadimg(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)     # 通過re模塊的split,將windows不支持的文件名符號,全部換成'.'
    for i in range(10):
        time.sleep(0.5) # 照顧別人服務器的帶寬,適當加點延時。。。加多少看你心情
        try:
            data = urlopen(link, timeout=10)
            tname = name+".jpg"
            with open(tname, "ab") as code:
                code.write(data.read())
            print(tname+" is done.")
            break
        except Exception:
            time.sleep(3) # 多數情況下,上面的語句中只有urlopen會出現無響應的異常,這時等待三秒,重新發送請求

二、圖集下載函數

更進一步的,我們要處理一個給定鏈接的圖集,首先我們寫一個下載當前頁面的主要圖片的功能

def downloaditem(link, ):
    html = urlopen(link, timeout=100)          # 打開鏈接
    bsObj = BeautifulSoup(html, "html.parser") # 用bs解析html

    for center in bsObj.findAll("center"):     # 找到所有的center標簽
        for img in center.findAll("img"):      # 找到其中包含img標簽的
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])
                                               # 下載image,并以圖片的alt屬性內容給圖片命名

但這還沒完,記得前面提到的頁面列表么,我們還要繼續下載 下一頁 的圖片。于是繼續

def downloaditem(link, ):
    html = urlopen(link, timeout=100)
    bsObj = BeautifulSoup(html, "html.parser")

    for center in bsObj.findAll("center"):
        for img in center.findAll("img"):
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])
#---------------------------------------------------------------------------
    page = bsObj.find("div", {"id":"pages"})   # 找到所有id屬性為pages的div標簽
    for a in page.findAll("a", {"class":"a1"}):
                                               # 找到其中class屬性為a1的a標簽
        if re.search(re.compile(r'下一頁'), a.getText()):
                                               # 如果標簽內容包含下一頁
            number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
                                               #用正則表達式匹配鏈接中的頁碼
            if number:                         #如果匹配成功,失敗時number為None
                link = number.group(0)         #提取頁面鏈接
                number = number.group(1)       #提取頁碼
                if number != page.find('span').getText():
                                               #如果鏈接的頁碼跟當前頁碼不同,則不是最后一頁,
                    print("download deeper...")#輸出提示信息
                    downloaditem(link)         #繼續下載下一頁

完善一下代碼,添加異常捕捉和延時

def downloaditem(link, ):
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)
            break
        except Exception:
            print("Url Erroe")
            time.sleep(2)

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")
            break
        except Exception:
            print("Soup Error")

    for center in bsObj.findAll("center"):
        for img in center.findAll("img"):
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])

    time.sleep(2)
    page = bsObj.find("div", {"id":"pages"})
    for a in page.findAll("a", {"class":"a1"}):
        if re.search(re.compile(r'下一頁'), a.getText()):
            number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
            if number:
                link = number.group(0)
                number = number.group(1)
                if number != page.find('span').getText():
                    print("download deeper...")
                    downloaditem(link)

三、獲取人名分類下的所有圖集鏈接

def downloadperson(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)                      # 跟圖片文件名原理一樣,替換被禁止的字符
    personitems = {}

    if not os.path.exists(name):               # 檢查這個人的文件夾之前有沒有創建
        os.mkdir(name)                         # 如果沒有就創建一個
    os.chdir(name)                             # 進入這個目錄

    html = urlopen(link, timeout=100)          # 打開鏈接
    bsObj = BeautifulSoup(html, "html.parser") # 用bs解析


    for boxs in bsObj.findAll("div", {"class":"boxs"}): # 找到裝載圖片集的<div>標簽
        for li in boxs.findAll("li"):                   # 處理每一個圖片集
            for p in li.findAll('p', {"class":"p_title"}): # 找到包含圖片鏈接的p標簽
                psn = p.find('a')
                personitems[psn.getText()] = psn.attrs['href'] # 用文件名作為key給字典添加圖集鏈接

    PATHtmp = os.getcwd()                      # PATHtmp是這一層人名文件夾的路徑
    for key in personitems:                    # 遍歷字典,下載每一個圖集
        print('\n', "downloading ", key, '\n')
        if not os.path.exists(key):            # 檢驗文件夾是否存在
            os.mkdir(key)
        os.chdir(key)                          # 進入文件夾
        downloaditem(personitems[key])         # 下載圖集
        os.chdir(PATHtmp)                      # 回到上一層目錄,這里用的絕對路徑,避免中途被打斷導致后面的下載也出現錯誤

    os.chdir(PATH)                             # 回到根目錄

完善代碼,添加異常捕捉和延時,這里因為同一個人沒有發現有多頁的情況,所以沒有處理頁面列表的代碼

def downloadperson(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)
    personitems = {}

    if not os.path.exists(name):
        os.mkdir(name)
    os.chdir(name)

    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)
            break
        except Exception:
            time.sleep(2)
            print("Url Erroe")

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")
            break
        except Exception:
            print("Soup Error")

    for boxs in bsObj.findAll("div", {"class":"boxs"}):
        for li in boxs.findAll("li"):
            try:
                for p in li.findAll('p', {"class":"p_title"}):
                    print('\n',p,'\n')
                    psn = p.find('a')
                    personitems[psn.getText()] = psn.attrs['href']
            except:
                print("Find Error")

    PATHtmp = os.getcwd()
    for key in personitems:
        print('\n', "downloading ", key, '\n')
        if not os.path.exists(key):
            os.mkdir(key)
        os.chdir(key)
        downloaditem(personitems[key])
        os.chdir(PATHtmp)

    os.chdir(PATH)

四、獲得分類下所有人名的分類鏈接

def getperson(link,):
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)           # 打開連接
            break
        except Exception:
            time.sleep(2)
            print("Url Erroe")

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")  # bs解析
            break
        except Exception:
            print("Soup Error")

    for boxs in bsObj.findAll("div", {"class":"boxs"}): # 獲取分類下包含圖集的標簽
        for li in boxs.findAll("li"):                   # 逐個圖集處理
            try:
                for a in li.findAll('p'):
                    print(a.getText())                  # 輸出圖集提示信息
                    name = re.search(re.compile(r'^模特:(.*?)$'), a.getText())
                    if name:
                        psn = a.find('a')               # 嘗試查找人名分類頁面鏈接
                        person[psn.getText()] = psn.attrs['href']
            except:                                     # 如果找不到分類頁面,則直接下載圖集
                print("downloading item..."+name.group(1))
                item = li.find('p', {"class":"p_title"}).find("a")
                print(item.getText())
                if not os.path.exists(name.group(1)):
                    os.mkdir(name.group(1))             # 創建人名文件夾
                os.chdir(name.group(1))                 # 進入人名文件夾
                print(name.group(1))
                name = item.getText()                   # 提取圖集
                name = re.split(forbidchar, name)       # 處理圖集名(文件夾名)
                name = '.'.join(name)
                if not os.path.exists(name):
                    os.mkdir(name)                      # 創建圖集文件夾
                os.chdir(name)                          # 進入圖集文件夾
                downloaditem(item.attrs['href'])        # 下載圖集
                os.chdir(PATH)                          # 回到根目錄


    time.sleep(3)                                       # 延時
    page = bsObj.find("div", {"id":"pages"})            # 處理下一頁問題,原理同downloaditem函數
    for a in page.findAll("a", {"class":"a1"}):
        if re.search(re.compile(r'下一頁'), a.getText()):
            number = re.search(re.compile(r"http://www\.meitulu\.com/t/.*?([0-9]*?)\.html"), a.attrs['href'])
            link = number.group(0)
            number = number.group(1)
            if number != page.find('span').getText():
                print("scrap deeper...")
                getperson(link)
                break

五、主函數

if __name__ == "__main__":
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen("http://www.meitulu.com", timeout=100) # 打開首頁鏈接
            break
        except Exception:
            print("Url Erroe")
            time.sleep(2)

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")            # bs解析
            break
        except Exception:
            print("Soup Error")

    for a in bsObj.find("li", {"id":"tag"}).find("ul", {"id":"tag_ul"}).findAll("a"):
        categaries[a.getText()] = a.attrs['href'] # 獲取所有分類首頁的鏈接,以分類名為key

    for key in categaries:
        time.sleep(3)
        print(i,"loading page..."+key)
        getperson(categaries[key])                # 獲取每一個分類下的所有人名鏈接

    for key in person:
        downloadperson(person[key], key)          # 下載每一個人名下的所有圖集

總結

完整代碼在這里:<a >simplespider.py</a>
我在代碼中延時加的比較多,所以運行起來有些慢,但畢竟這只是個練習,照顧一下別人服務器比較好= =。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容