1. 程式人生 > >Python3 大型網路爬蟲實戰 003 — scrapy 大型靜態圖片網站爬蟲專案實戰 — 實戰:爬取 169美女圖片網 高清圖片

Python3 大型網路爬蟲實戰 003 — scrapy 大型靜態圖片網站爬蟲專案實戰 — 實戰:爬取 169美女圖片網 高清圖片

開發環境

  • Python第三方庫:lxml、Twisted、pywin32、scrapy
  • Python 版本:python-3.5.0-amd64
  • PyCharm軟體版本:pycharm-professional-2016.1.4
  • 電腦系統:Windows 10 64位

如果你還沒有搭建好開發環境,請到這篇部落格

  • 本篇部落格原始碼GitHub裡:這裡

這一篇部落格的目的是爬取 169美女圖片網 裡面的所有的“西洋美女”的高清圖片。

Alt text

爬蟲程式設計思路:

2 . 接著在得到的URL連結網頁裡面得到裡面所有高清圖片的下載地址,進行下載。

3 . 得到所有 “西洋美女” 網頁的頁數。

Alt text

觀察網頁 和 網頁原始碼

Alt text

3 . 觀察這個頁面,在頁面最下面,顯示了,當前一共311頁。

Alt text

5 . 現在,我們隨便點選一個圖片,進去看看這個美女的高清圖片集。

裡面都是高清的圖片,並且有很多,並且,不止一頁。就我隨機點選的這個美女的連結就有11頁,並且一頁裡面有5張左右的高清圖片。

Alt text

6 . 並且它的每頁的網址也是有規律的。

好了,現在這個目標網頁,我們已經分析完了。現在就可以程式設計。

寫程式

原始碼GitHub裡:這裡

接下來我們為大家講解大型圖片爬蟲專案編寫實戰。

Step 1 .

建立一個Scrapy爬蟲專案:

scrapy startproject secondDemo

Alt text

建立一個scrapy爬蟲檔案,我們就在這個檔案裡面寫爬取目標網站裡面圖片的爬蟲程式。

cd secondDemo
scrapy genspider -t basic pic_169bb 169bb.com

Alt text

PyCharm 軟體開啟剛剛建立的 secondDemo 工程。

Alt text

Step 2 . items.py 檔案裡面的SeconddemoItem()函式裡面建立一個物件,這個物件在其他的檔案裡面會使用到。

Alt text

Step 3 . 現在開始爬蟲的編寫。進入pic_169bb.py檔案。

爬蟲(pic_169bb.py

檔案)會自動的先爬首頁(http://169bb.com/),爬完首頁之後,會自動的進入parse()回撥函式。

這個回撥函式中,我們需要寫些東西。

先獲取所有欄目的名字和地址。

Alt text

檢視原始碼:

Alt text

pic_169bb.py 檔案中的 parse()回撥函式中新增下面的程式碼:

        urldata = response.xpath("/html/body/div[@class='header']/div[@class='hd_nav']/div[@class='w1000']//a/@href").extract()
        print(urldata)

Alt text

現在執行一下,輸出:

Alt text

繼續進行下一次的爬取:

先匯入一個模組

from scrapy.http import Request

爬取子欄目的第一頁,即西洋美女網址的第一頁。

        xiyangurldata = urldata[4]  # 獲取西洋美女首頁網址
        print(xiyangurldata)
        yield Request(url=xiyangurldata, callback=self.next)

    def next(self, response):
        pass

Alt text

    def next(self, response):
        page_title_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@alt").extract()
        print(page_title_list)
        page_url_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@href").extract()
        print(page_url_list)
        pass

Alt text

執行輸出一下:

Alt text

Alt text

        page_num = response.xpath("//span[@class='pageinfo']//strong/text()").extract()[0] # 得到西洋美女總頁數
        print(page_num)
        print(response.url)
        for i in range(1, int(page_num)+1):
            page_url = response.url + 'list_4_'+ str(i) + '.html' # 得到西洋美女每一個頁面的網址
            print(page_url)
        pass

Alt text

執行輸出一下:

Alt text

Alt text

繼續下一次:

        for i in range(1, int(page_num)+1):
            page_url = response.url + 'list_4_'+ str(i) + '.html' # 得到西洋美女每一個頁面的網址
            print(page_url)
            yield Request(url=page_url, callback=self.next2)
        pass

    def next2(self, response):
        pass

Alt text

現在獲取每一個美女的網頁網址:

    def next2(self, response):
        page_title_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@alt").extract()
        # print(page_title_list)
        page_url_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@href").extract()
        # print(page_url_list)

        for i in range(0, len(page_url_list)):
            gril_page_url = page_url_list[i]
            print(gril_page_url)
            yield Request(url=gril_page_url, callback=self.next3)
        pass

    def next3(self, response):
        pass

Alt text

執行程式看看:

Alt text

next3() 這個回撥函式的功能就是得到一個美女網頁裡面的所有的頁面的網址。

有的美女的網頁裡面只有一個頁面,有的美女的網頁裡面有多個頁面:

Alt text

Alt text

可以統一解決。

測試:

Alt text

Alt text

Alt text

輸出:

Alt text

Alt text

Alt text

同樣的回撥函式

輸出:

Alt text

所以,我們可以這樣寫程式:

當得到的頁碼為-3,說明這個美女的網頁是單頁的;如果得到的頁碼數不等於-3,說明這個美女的網頁是多也的。

測試程式:

對於多頁面的美女網頁網址

Alt text

Alt text

執行輸出:

D:\WorkSpace\python_ws\python-large-web-crawler\secondDemo>scrapy crawl pic_169bb --nolog
10
http://www.169bb.com/xiyangmeinv/2016/0717/36463.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_5.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_10.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_9.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_6.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_8.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_7.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_3.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_4.html
http://www.169bb.com/xiyangmeinv/2016/0717/36463_2.html

D:\WorkSpace\python_ws\python-large-web-crawler\secondDemo>

對於單頁面的美女網頁:

    def parse(self, response):
        ...
        yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.demo)

回撥函輸一樣。

執行輸出:

D:\WorkSpace\python_ws\python-large-web-crawler\secondDemo>scrapy crawl pic_169bb --nolog
-3
http://www.169bb.com/xiyangmeinv/2016/0103/2268.html

D:\WorkSpace\python_ws\python-large-web-crawler\secondDemo>

成功。

所以現在的爬蟲程式碼應該是這樣的:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request

class Pic169bbSpider(scrapy.Spider):
    name = "pic_169bb"
    allowed_domains = ["169bb.com"]
    start_urls = ['http://169bb.com/']

    def parse(self, response):
        title_list = response.xpath("/html/body/div[@class='header']/div[@class='hd_nav']/div[@class='w1000']//a/text()").extract()
        # print(title_list)
        urldata = response.xpath("/html/body/div[@class='header']/div[@class='hd_nav']/div[@class='w1000']//a/@href").extract()
        #print(urldata)
        xiyang_title = title_list[4] # 獲取西洋美女標籤的文字內容
        xiyang_urldata = urldata[4]  # 獲取西洋美女首頁網址
        # print(xiyang_title, xiyang_urldata)
        yield Request(url=xiyang_urldata, callback=self.next)
        # yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0717/36463.html', callback=self.demo)
        # yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.demo)

    def next(self, response):
        page_num_str = response.xpath("//span[@class='pageinfo']//strong/text()").extract()[0] # 得到西洋美女總頁數
        # print(page_num_str)
        # print(response.url)
        for i in range(1, int(page_num_str)+1):
            page_url = response.url + 'list_4_'+ str(i) + '.html' # 得到西洋美女每一個頁面的網址
            # print(page_url)
            yield Request(url=page_url, callback=self.next2)
        pass

    def next2(self, response):
        page_title_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@alt").extract()
        # print(page_title_list)
        page_url_list = response.xpath("/html/body//div[@class='w1000 box03']/ul[@class='product01']//li/a/@href").extract()
        # print(page_url_list)

        for i in range(0, len(page_url_list)):
            gril_page_url = page_url_list[i] # 得到西洋美女頁面裡面每一個美女的網頁網址
            print(gril_page_url)
            yield Request(url=gril_page_url, callback=self.next3)
        pass

    def next3(self, response):
        rela_pages_list = response.xpath("//div[@class='dede_pages']/ul//li/a/text()").extract()
        pages_num = len(rela_pages_list) - 3
        # print(pages_num)
        self.getPic(response)
        if pages_num == -3:
            # pages_num = 1
            return
        for i in range(2, pages_num+1):
            girl_page_url = response.url.replace('.html', '_') + str(i) + '.html'
            # print(girl_page_url)
            yield Request(url=girl_page_url, callback=self.next4)
        pass

    # def demo(self, response):
    # #     rela_pages_list = response.xpath("//div[@class='dede_pages']/ul//li/a/text()").extract()
    # #     pages_num = len(rela_pages_list)-3
    # #     print(pages_num)
    # #     pass
    #     rela_pages_list = response.xpath("//div[@class='dede_pages']/ul//li/a/text()").extract()
    #     pages_num = len(rela_pages_list) - 3
    #     # print(pages_num)
    #     self.getPic(response)
    #     if pages_num == -3:
    #         # pages_num = 1
    #         return
    #     for i in range(2, pages_num+1):
    #         girl_page_url = response.url.replace('.html', '_') + str(i) + '.html'
    #         # print(girl_page_url)
    #         yield Request(url=girl_page_url, callback=self.next4)
    #     pass

    def next4(self, response):
        self.getPic(response)
        pass

    def getPic(self, response):
        print(response.url)
        pass

現在,我們需要在getPic() 函式中獲取每一個美女網頁的每一個頁面裡面的所有高清圖片。

Alt text

    def getPic(self, response):
        # print(response.url)
        item = SeconddemoItem()
        item['url'] = response.xpath("//div[@class='big-pic']/div[@class='big_img']//p/img/@src").extract()
        print(item['url'])
        pass

測試執行:

測試多頁的美女網頁:

先將parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0717/36463.html', callback=self.demo)

Alt text

測試單頁的美女網頁:

parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.demo)

Alt text

成功。

下載高清圖片

OK,現在我們就已經得到了所有西洋美女的所有高清圖片的下載地址,我們在piplines.py 檔案中使用它們。

刪除了next4()回撥函式。在demo()回撥函式裡面呼叫的都是getPic()回撥函式。

    def demo(self, response):
    #     rela_pages_list = response.xpath("//div[@class='dede_pages']/ul//li/a/text()").extract()
    #     pages_num = len(rela_pages_list)-3
    #     print(pages_num)
    #     pass
        rela_pages_list = response.xpath("//div[@class='dede_pages']/ul//li/a/text()").extract()
        pages_num = len(rela_pages_list) - 3
        # print(pages_num)
        self.getPic(response)
        if pages_num == -3:
            # pages_num = 1
            return
        for i in range(2, pages_num+1):
            girl_page_url = response.url.replace('.html', '_') + str(i) + '.html'
            # print(girl_page_url)
            yield Request(url=girl_page_url, callback=self.getPic)
        pass

    # error : yield 經過了一箇中間函式,執行就有問題。我現在還不知道為什麼
    # def next4(self, response):
    #     self.getPic(response)
    #     pass

並將getPic()函式裡面的item寫到生成器裡面:

    def getPic(self, response):
        # print(response.url)
        item = SeconddemoItem()
        item['url'] = response.xpath("//div[@class='big-pic']/div[@class='big_img']//p/img/@src").extract()
        # print(item['url'])
        # pass
        yield item

測試:

測試單頁的美女網頁:

parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.getPic)

成功。

測試多頁的美女網頁:

先將parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0717/36463.html', callback=self.demo)

Alt text

也算算成功,因為有重複的檔名,所以自動替換,所以這裡需要做修改。

我們可以使用美女圖片網址裡面的數字作為圖片檔名的固定字首來給圖片命名,使用正則表示式獲取網址的數字。

pipelines.py 檔案 中的 process_item() 函式中使用正則表示式得到圖片下載網址的數字:

import re
import urllib.request

class SeconddemoPipeline(object):
    def process_item(self, item, spider):
        # print(len(item['url']))
        for i in range(0, len(item['url'])):
            this_url = item['url'][i]
            id = re.findall('http://724.169pp.net/169mm/(.*?).jpg', this_url)[0]
            id = id.replace('/', '_')
            print(id)
            # file = 'D:/WorkSpace/python_ws/python-large-web-crawler/xiyangmeinv/' + str(i) + '.jpg'
            # print('Downloading :' , file)
            # urllib.request.urlretrieve(this_url, filename=file)
            # print('Final Download :' , file)
        return item

Alt text

要想使用 pipelines.py 檔案 中的 SeconddemoPipeline 類,需要在 settings.py 檔案裡面設定 ITEM_PIPELINES 項:

ITEM_PIPELINES = {
   'secondDemo.pipelines.SeconddemoPipeline': 300,
}

Alt text

這裡,我有一件事情不懂,關於正則表示式的: 網址裡面的. 也是正則表示式中的工具字元,也是資料中中的內容,那麼正則表示式是如何分辨它在這裡是功能字元還是內容字元?

pic_169bb.py 檔案的demo() 回撥函式中,這樣寫才能獲取到美女網頁的第一頁的圖片地址:

        # error
        # self.getPic(response)
        # succes 為啥將下面的程式碼用self.getPic(response)的形式不能正常的獲取到,而使用下面的程式碼卻能獲取到?
        item = SeconddemoItem()
        item['url'] = response.xpath("//div[@class='big-pic']/div[@class='big_img']//p/img/@src").extract()
        # print(item['url'])
        # pass
        yield item

Alt text

執行程式試試:

測試單頁的美女網頁:

parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.getPic)

Alt text

成功。

測試多頁的美女網頁:

先將parse()函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0717/36463.html', callback=self.demo)

Alt text

都成功。

先測試下載圖片:

pipelines.py 檔案 中的 SeconddemoPipeline 類的process_item() 函式裡面,新增程式碼:

    def process_item(self, item, spider):
        # print(len(item['url']))
        for i in range(0, len(item['url'])):
            this_url = item['url'][i]
            id = re.findall('http://724.169pp.net/169mm/(.*?).jpg', this_url)[0]
            id = id.replace('/', '_')
            # print(id)
            file = 'D:/WorkSpace/python_ws/python-large-web-crawler/xiyangmeinv/' + id + '.jpg'
            print('Downloading :' , file)
            urllib.request.urlretrieve(this_url, filename=file)
            print('Final Download :' , file)
        return item

Alt text

執行程式,沒有毛病:(除了下載速度有點慢)

Alt text

下載 169美女圖片網 的所有西洋美女的圖片

pic_169bb.py檔案裡, 將parse()函式的最後一行改為:yield Request(url=xiyang_urldata, callback=self.next)

Alt text

demo() 函式裡面的所有程式碼複製一份到 next3()函式裡:

Alt text

現在,執行程式:(最終的程式)

Alt text

Alt text

成功!

防反爬技術

Step 4 . 不遵循 robots.txt 協議。

settings.py 檔案裡面的 ROBOTSTXT_OBEY 項設定為:False

Alt text

Step 6 . 模仿瀏覽器

settings.py 檔案裡面的 USER_AGENT 項設定為:瀏覽器的使用者代理資訊。

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0'

Step 7 . 禁止快取

settings.py 檔案裡面的 COOKIES_ENABLED 項設定為:False

COOKIES_ENABLED = False

搞定

需要升級的地方:(2016-11-27 19:34:34)

  1. 在易錯的程式碼段加上異常檢測程式
  2. 在下載圖片的程式碼加上:超時異常檢測程式
  3. 記錄成功下載的、超時失敗下載的、連結失敗下載的 資訊
  4. 新增斷點續下功能。