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美女圖片網 裡面的所有的“西洋美女”的高清圖片。
爬蟲程式設計思路:
2 . 接著在得到的URL連結網頁裡面得到裡面所有高清圖片的下載地址,進行下載。
3 . 得到所有 “西洋美女” 網頁的頁數。
觀察網頁 和 網頁原始碼
3 . 觀察這個頁面,在頁面最下面,顯示了,當前一共311頁。
5 . 現在,我們隨便點選一個圖片,進去看看這個美女的高清圖片集。
裡面都是高清的圖片,並且有很多,並且,不止一頁。就我隨機點選的這個美女的連結就有11頁,並且一頁裡面有5張左右的高清圖片。
6 . 並且它的每頁的網址也是有規律的。
好了,現在這個目標網頁,我們已經分析完了。現在就可以程式設計。
寫程式
原始碼GitHub裡:這裡。
接下來我們為大家講解大型圖片爬蟲專案編寫實戰。
Step 1 .
建立一個Scrapy爬蟲專案:
scrapy startproject secondDemo
建立一個scrapy爬蟲檔案,我們就在這個檔案裡面寫爬取目標網站裡面圖片的爬蟲程式。
cd secondDemo
scrapy genspider -t basic pic_169bb 169bb.com
用 PyCharm 軟體開啟剛剛建立的 secondDemo
工程。
Step 2 . 在 items.py
檔案裡面的SeconddemoItem()
函式裡面建立一個物件,這個物件在其他的檔案裡面會使用到。
Step 3 . 現在開始爬蟲的編寫。進入pic_169bb.py
檔案。
爬蟲(pic_169bb.py
parse()
回撥函式。
這個回撥函式中,我們需要寫些東西。
先獲取所有欄目的名字和地址。
檢視原始碼:
在pic_169bb.py
檔案中的 parse()
回撥函式中新增下面的程式碼:
urldata = response.xpath("/html/body/div[@class='header']/div[@class='hd_nav']/div[@class='w1000']//a/@href").extract()
print(urldata)
現在執行一下,輸出:
繼續進行下一次的爬取:
先匯入一個模組
from scrapy.http import Request
爬取子欄目的第一頁,即西洋美女網址的第一頁。
xiyangurldata = urldata[4] # 獲取西洋美女首頁網址
print(xiyangurldata)
yield Request(url=xiyangurldata, callback=self.next)
def next(self, response):
pass
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
執行輸出一下:
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
執行輸出一下:
繼續下一次:
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
現在獲取每一個美女的網頁網址:
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
執行程式看看:
next3()
這個回撥函式的功能就是得到一個美女網頁裡面的所有的頁面的網址。
有的美女的網頁裡面只有一個頁面,有的美女的網頁裡面有多個頁面:
可以統一解決。
測試:
輸出:
同樣的回撥函式
輸出:
所以,我們可以這樣寫程式:
當得到的頁碼為-3,說明這個美女的網頁是單頁的;如果得到的頁碼數不等於-3,說明這個美女的網頁是多也的。
測試程式:
對於多頁面的美女網頁網址
執行輸出:
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()
函式中獲取每一個美女網頁的每一個頁面裡面的所有高清圖片。
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)
測試單頁的美女網頁:
將parse()
函式的最後一行改為:yield Request(url='http://www.169bb.com/xiyangmeinv/2016/0103/2268.html', callback=self.demo)
成功。
下載高清圖片
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)
也算算成功,因為有重複的檔名,所以自動替換,所以這裡需要做修改。
我們可以使用美女圖片網址裡面的數字作為圖片檔名的固定字首來給圖片命名,使用正則表示式獲取網址的數字。
在 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
要想使用 pipelines.py
檔案 中的 SeconddemoPipeline
類,需要在 settings.py
檔案裡面設定 ITEM_PIPELINES
項:
ITEM_PIPELINES = {
'secondDemo.pipelines.SeconddemoPipeline': 300,
}
這裡,我有一件事情不懂,關於正則表示式的: 網址裡面的
.
也是正則表示式中的工具字元,也是資料中中的內容,那麼正則表示式是如何分辨它在這裡是功能字元還是內容字元?
在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
執行程式試試:
測試單頁的美女網頁:
將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)
都成功。
先測試下載圖片:
將 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
執行程式,沒有毛病:(除了下載速度有點慢)
下載 169美女圖片網 的所有西洋美女的圖片
在 pic_169bb.py
檔案裡, 將parse()
函式的最後一行改為:yield Request(url=xiyang_urldata, callback=self.next)
將 demo()
函式裡面的所有程式碼複製一份到 next3()
函式裡:
現在,執行程式:(最終的程式)
成功!
防反爬技術
Step 4 . 不遵循 robots.txt
協議。
將 settings.py
檔案裡面的 ROBOTSTXT_OBEY
項設定為:False
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)
- 在易錯的程式碼段加上異常檢測程式
- 在下載圖片的程式碼加上:超時異常檢測程式
- 記錄成功下載的、超時失敗下載的、連結失敗下載的 資訊
- 新增斷點續下功能。