若是是沒有接觸過爬蟲的人可能會有些許疑惑,爬蟲是個什麼東西呢?其實爬蟲的概念很簡單,在互聯網時代,萬維網已然是大量信息的載體,如何有效地利用並提取這些信息是一個巨大的挑戰。當咱們使用瀏覽器對某個網站發送請求時,服務器會響應HTML
文本並由瀏覽器來進行渲染顯示。爬蟲正是利用了這一點,經過程序模擬用戶的請求,來得到HTML
的內容,並從中提取須要的數據和信息。若是把網絡想象成一張蜘蛛網,爬蟲程序則像是蜘蛛網上的蜘蛛,不斷地爬取數據與信息。html
爬蟲的概念很是簡單易懂,利用python
內置的urllib
庫均可以實現一個簡單的爬蟲,下面的代碼是一個很是簡單的爬蟲,只要有基本的python
知識應該都能看懂。它會收集一個頁面中的全部<a>
標籤(沒有作任何規則判斷)中的連接,而後順着這些連接不斷地進行深度搜索。html5
from bs4 import BeautifulSoup
import urllib
import os
from datetime import datetime
# 網頁的實體類,只含有兩個屬性,url和標題
class Page(object):
def __init__(self,url,title):
self._url = url
self._title = title
def __str__(self):
return '[Url]: %s [Title]: %s' %(self._url,self._title)
__repr__ = __str__
@property
def url(self):
return self._url
@property
def title(self):
return self._title
@url.setter
def url(self,value):
if not isinstance(value,str):
raise ValueError('url must be a string!')
if value == '':
raise ValueError('url must be not empty!')
self._url = value
@title.setter
def title(self,value):
if not isinstance(value,str):
raise ValueError('title must be a string!')
if value == '':
raise ValueError('title must be not empty!')
self._title = value
class Spider(object):
def __init__(self,init_page):
self._init_page = init_page # 種子網頁,也就是爬蟲的入口
self._pages = []
self._soup = None # BeautifulSoup 一個用來解析HTML的解析器
def crawl(self):
start_time = datetime.now()
print('[Start Time]: %s' % start_time)
start_timestamp = start_time.timestamp()
tocrawl = [self._init_page] # 記錄將要爬取的網頁
crawled = [] # 記錄已經爬取過的網頁
# 不斷循環,直到將這張圖搜索完畢
while tocrawl:
page = tocrawl.pop()
if page not in crawled:
self._init_soup(page)
self._packaging_to_pages(page)
links = self._extract_links()
self._union_list(tocrawl,links)
crawled.append(page)
self._write_to_curdir()
end_time = datetime.now()
print('[End Time]: %s' % end_time)
end_timestamp = end_time.timestamp()
print('[Total Time Consuming]: %f.3s' % (start_timestamp - end_timestamp) / 1000)
def _init_soup(self,page):
page_content = None
try:
# urllib能夠模擬用戶請求,得到響應的HTML文本內容
page_content = urllib.request.urlopen(page).read()
except:
page_content = ''
# 初始化BeautifulSoup,參數二是使用到的解析器名字
self._soup = BeautifulSoup(page_content,'lxml')
def _extract_links(self):
a_tags = self._soup.find_all('a') # 找到全部a標籤
links = []
# 收集全部a標籤中的連接
for a_tag in a_tags:
links.append(a_tag.get('href'))
return links
def _packaging_to_pages(self,page):
title_string = ''
try:
title_string = self._soup.title.string # 得到title標籤中的文本內容
except AttributeError as e :
print(e)
page_obj = Page(page,title_string)
print(page_obj)
self._pages.append(page_obj)
# 將爬取到的全部信息寫入到當前目錄下的out.txt文件
def _write_to_curdir(self):
cur_path = os.path.join(os.path.abspath('.'),'out.txt')
print('Start write to %s' % cur_path)
with open(cur_path,'w') as f:
f.write(self._pages)
# 將dest中的不存在於src的元素合併到src
def _union_list(self,src,dest):
for dest_val in dest:
if dest_val not in src:
src.append(dest_val)
@property
def init_page(self):
return self._init_page
@property
def pages(self):
return self._pages
def test():
spider = Spider('https://sylvanassun.github.io/')
spider.crawl()
if __name__ == '__main__':
test()複製代碼
可是咱們若是想要實現一個性能高效的爬蟲,那須要的複雜度也會增加,本文旨在快速實現,因此咱們須要藉助他人實現的爬蟲框架來當作腳手架,在這之上來構建咱們的圖片爬蟲(若是有時間的話固然也鼓勵本身造輪子啦)。python
本文做者爲: SylvanasSun(sylvanas.sun@gmail.com).轉載請務必將下面這段話置於文章開頭處(保留超連接).
本文首發自SylvanasSun Blog,原文連接: sylvanassun.github.io/2017/09/20/…git
BeautifulSoup是一個用於從HTML
和XML
中提取數據的python
庫。Beautiful Soup自動將輸入文檔轉換爲Unicode編碼,輸出文檔轉換爲utf-8編碼。你不須要考慮編碼方式,除非文檔沒有指定一個編碼方式,這時,Beautiful Soup就不能自動識別編碼方式了。而後,你僅僅須要說明一下原始編碼方式就能夠了。程序員
利用好BeautifulSoup能夠爲咱們省去許多編寫正則表達式的時間,若是當你須要更精準地進行搜索時,BeautifulSoup也支持使用正則表達式進行查詢。github
BeautifulSoup3已經中止維護了,如今基本使用的都是BeautifulSoup4,安裝BeautifulSoup4很簡單,只須要執行如下的命令。正則表達式
pip install beautifulsoup4複製代碼
而後從bs4
模塊中導入BeautifulSoup對象,並建立這個對象。數據庫
from bs4 import BeautifulSoup
soup = BeautifulSoup(body,'lxml')複製代碼
建立BeautifulSoup對象須要傳入兩個參數,第一個是須要進行解析的HTML
內容,第二個參數爲解析器的名字(若是不傳入這個參數,BeautifulSoup會默認使用python
內置的解析器html.parser
)。BeautifulSoup支持多種解析器,有lxml
、html5lib
、html.parser
。json
第三方解析器須要用戶本身安裝,本文中使用的是lxml
解析器,安裝命令以下(它還須要先安裝C語言庫)。windows
pip install lxml複製代碼
下面以一個例子演示使用BeautifulSoup的基本方式,若是還想了解更多能夠去參考BeautifulSoup文檔。
from bs4 import BeautifulSoup
html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """
soup = BeautifulSoup(html,'lxml')
# 格式化輸出soup中的內容
print(soup.prettify())
# 能夠經過.操做符來訪問標籤對象
title = soup.title
print(title)
p = soup.p
print(p)
# 得到title標籤中的文本內容,這2個方法獲得的結果是同樣的
print(title.text)
print(title.get_text())
# 得到head標籤的全部子節點,contents返回的是一個列表,children返回的是一個迭代器
head = soup.head
print(head.contents)
print(head.children)
# 得到全部a標籤,並輸出每一個a標籤href屬性中的內容
a_tags = soup.find_all('a')
for a_tag in a_tags:
print(a_tag['href'])
# find函數與find_all同樣,只不過返回的是找到的第一個標籤
print(soup.find('a')['href'])
# 根據屬性查找,這2個方法獲得的結果是同樣的
print(soup.find('p',class_='title'))
print(soup.find('p',attrs={'class': 'title'}))複製代碼
Scrapy
是一個功能強大的爬蟲框架,它已經實現了一個性能高效的爬蟲結構,並提供了不少供程序員自定義的配置。使用Scrapy
只須要在它的規則上編寫咱們的爬蟲邏輯便可。
首先須要先安裝Scrapy
,執行命令pip install scrapy
。而後再執行命令scrapy startproject 你的項目名
來生成Scrapy
的基本項目文件夾。生成的項目結構以下。
你的項目名/
scrapy.cfg
你的項目名/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...複製代碼
scrapy.cfg
: 項目的配置文件。
items.py
:物品模塊,用戶須要在這個模塊中定義數據封裝的實體類。
pipelines.py
:管道模塊,用戶須要在這個模塊中定義處理數據的邏輯(如存儲到數據庫等)。
settings.py
:這個模塊定義了整個項目中的各類配置變量。
spiders/
:在這個包中定義用戶本身的爬蟲模塊。
啓動Scrapy
的爬蟲也很簡單,只須要執行命令scrapy crawl 你的爬蟲名
。下面介紹Scrapy
中的關鍵模塊的演示案例,若是想要了解有關Scrapy
的更多信息,請參考Scrapy官方文檔。
items
模塊主要是爲了將爬取到的非結構化數據封裝到一個結構化對象中,自定義的item
類必須繼承自scrapy.Item
,且每一個屬性都要賦值爲scrapy.Field()
。
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()複製代碼
操做item
對象就像操做一個dict
對象同樣簡單。
product = Product()
# 對屬性賦值
product['name'] = 'Sylvanas'
product['price'] = 998
# 得到屬性
print(product['name'])
print(product['price'])複製代碼
當一個Item
經由爬蟲封裝以後將會到達Pipeline
類,你能夠定義本身的Pipeline
類來決定將Item
的處理策略。
每一個Pipeline
能夠實現如下函數。
process_item(item, spider)
: 每一個Pipeline
都會調用此函數來處理Item
,這個函數必須返回一個Item
,若是在處理過程當中碰見錯誤,能夠拋出DropItem
異常。
open_spider(spider)
: 當spider
開始時將會調用此函數,能夠利用這個函數進行打開文件等操做。
close_spider(spider)
:當spider
關閉時將會調用此函數,能夠利用這個函數對IO
資源進行關閉。
from_crawler(cls, crawler)
: 這個函數用於獲取settings.py
模塊中的屬性。注意這個函數是一個類方法。
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def __init__(self, HELLO):
self.HELLO = HELLO
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings # 從crawler中得到settings
return cls(settings['HELLO']) # 返回settings中的屬性,將由__init__函數接收複製代碼
當定義完你的Pipeline
後,還須要在settings.py
中對你的Pipeline
進行設置。
ITEM_PIPELINES = {
# 後面跟的數字是優先級別
'pipeline類的全路徑': 300,
}複製代碼
在spiders
模塊中,用戶能夠經過自定義Spider
類來制定本身的爬蟲邏輯與數據封裝策略。每一個Spider
都必須繼承自class scrapy.spider.Spider
,這是Scrapy
中最簡單的爬蟲基類,它沒有什麼特殊功能,Scrapy
也提供了其餘功能不一樣的Spider
類供用戶選擇,這裏就很少敘述了,能夠去參考官方文檔。
用戶能夠經過如下屬性來自定義配置Spider
:
name
: 這是Spider
的名稱,Scrapy
須要經過這個屬性來定位Spider
並啓動爬蟲,它是惟一且必需的。
allowed_domains
: 這個屬性規定了Spider
容許爬取的域名。
start_urls
: Spider
開始時將抓取的網頁列表。
start_requests()
: 該函數是Spider
開始抓取時啓動的函數,它只會被調用一次,有的網站必需要求用戶登陸,可使用這個函數先進行模擬登陸。
make_requests_from_url(url)
: 該函數接收一個url
並返回Request
對象。除非重寫該函數,不然它會默認以parse(response)
函數做爲回調函數,並啓用dont_filter
參數(這個參數是用於過濾重複url
的)。
parse(response)
: 當請求沒有設置回調函數時,則會默認調用parse(response)
。
log(message[, level, component])
: 用於記錄日誌。
closed(reason)
: 當Spider
關閉時調用。
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
self.log('A response from %s just arrived!' % response.url)複製代碼
Requests
也是一個第三方python
庫,它比python
內置的urllib
更加簡單好用。只須要安裝(pip install requests
),而後導包後,便可輕鬆對網站發起請求。
import requests
# 支持http的各類類型請求
r = requests.post("http://httpbin.org/post")
r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")
# 得到響應內容
r.text # 返回文本
r.content # 返回字節
r.raw # 返回原始內容
r.json() # 返回json複製代碼
關於更多的參數與內容請參考Requests文檔。
BloomFilter
是一個用於過濾重複數據的數據結構,咱們可使用它來對重複的url
進行過濾。本文使用的BloomFilter
來自於python-bloomfilter,其餘操做系統用戶請使用pip install pybloom
命令安裝,windows用戶請使用pip install pybloom-live
(原版對windows不友好)。
介紹了須要的依賴庫以後,咱們終於能夠開始實現本身的圖片爬蟲了。咱們的目標是爬https://www.deviantart.com/
網站中的圖片,在寫爬蟲程序以前,還須要先分析一下頁面的HTML
結構,這樣才能針對性地找到圖片的源地址。
爲了保證爬到的圖片的質量,我決定從熱門頁面開始爬,連接爲https://www.deviantart.com/whats-hot/
。
打開瀏覽器的開發者工具後,能夠發現每一個圖片都是由一個a
標籤組成,每一個a
標籤的class
爲torpedo-thumb-link
,而這個a
標籤的href
正好就是這張圖片的詳情頁面(若是咱們從這裏就開始爬圖片的話,那麼爬到的可都只是縮略圖)。
進入到詳情頁後,不要立刻爬取當前圖片的源地址,由於當前頁顯示的圖片並非原始格式,咱們對圖片雙擊放大以後再使用開發者工具抓到這個圖片所在的img
標籤後,再讓爬蟲獲取這個標籤中的源地址。
在得到圖片的源地址以後,個人策略是讓爬蟲繼續爬取該頁中推薦的更多圖片,經過開發者工具,能夠發現這些圖片都被封裝在一個class
爲tt-crop thumb
的div
標籤中,而該標籤裏的第一個a
子標籤正好就是這個圖片的詳情頁連接。
在對網頁的HTML
進行分析以後,能夠開始寫程序了,首先先用Scrapy
的命令來初始化項目。以後在settings.py
中作以下配置。
# 這個是網絡爬蟲協議,爬蟲訪問網站時都會檢查是否有robots.txt文件,
# 而後根據文件中的內容選擇性地進行爬取,咱們這裏設置爲False即不檢查robots.txt
ROBOTSTXT_OBEY = False
# 圖片下載的根目錄路徑
IMAGES_STORE = '.'
# 圖片最大下載數量,當下載的圖片達到這個數字時,將會手動關閉爬蟲
MAXIMUM_IMAGE_NUMBER = 10000複製代碼
而後定義咱們的Item
。
import scrapy
class DeviantArtSpiderItem(scrapy.Item):
author = scrapy.Field() # 做者名
image_name = scrapy.Field() # 圖片名
image_id = scrapy.Field() # 圖片id
image_src = scrapy.Field() # 圖片的源地址複製代碼
建立本身的spider
模塊與Spider
類。
import requests
from bs4 import BeautifulSoup
# this import package is right,if PyCharm give out warning please ignore
from deviant_art_spider.items import DeviantArtSpiderItem
from pybloom_live import BloomFilter
from scrapy.contrib.linkextractors.lxmlhtml import LxmlLinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.http import Request
class DeviantArtImageSpider(CrawlSpider):
name = 'deviant_art_image_spider'
# 我不想讓scrapy幫助過濾因此設置爲空
allowed_domains = ''
start_urls = ['https://www.deviantart.com/whats-hot/']
rules = (
Rule(LxmlLinkExtractor(
allow={'https://www.deviantart.com/whats-hot/[\?\w+=\d+]*', }),
callback='parse_page', # 設置回調函數
follow=True # 容許爬蟲不斷地跟隨連接進行爬取
),
)
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36"
" (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
"Referer": "https://www.deviantart.com/"
}
# 初始化BloomFilter
filter = BloomFilter(capacity=15000)複製代碼
DeviantArtImageSpider
繼承自CrawlSpider
,該類是Scrapy
最經常使用的Spider
類,它經過Rule
類來定義爬取連接的規則,上述代碼中使用了正則表達式https://www.deviantart.com/whats-hot/[\?\w+=\d+]*
,這個正則表達式將訪問每一頁的熱門頁面。
爬蟲啓動時將會先訪問熱門頁面,請求獲得響應以後會調用回調函數,咱們須要在這個回調函數中獲取上述分析中獲得的<a class = 'torpedo-thumb-link'>
標籤,而後抽取出每張圖片的詳情頁連接。
def parse_page(self, response):
soup = self._init_soup(response, '[PREPARING PARSE PAGE]')
if soup is None:
return None
# 找到全部class爲torpedo-thumb-link的a標籤
all_a_tag = soup.find_all('a', class_='torpedo-thumb-link')
if all_a_tag is not None and len(all_a_tag) > 0:
for a_tag in all_a_tag:
# 提取圖片詳情頁,而後對詳情頁連接發起請求,並設置回調函數
detail_link = a_tag['href']
request = Request(
url=detail_link,
headers=self.headers,
callback=self.parse_detail_page
)
# 經過request與response對象來傳遞Item
request.meta['item'] = DeviantArtSpiderItem()
yield request
else:
self.logger.debug('[PARSE FAILED] get <a> tag failed')
return None
# 初始化BeautifulSoup對象
def _init_soup(self, response, log):
url = response.url
self.headers['Referer'] = url
self.logger.debug(log + ' ' + url)
body = requests.get(url, headers=self.headers, timeout=2).content
soup = BeautifulSoup(body, 'lxml')
if soup is None:
self.logger.debug('[PARSE FAILED] read %s body failed' % url)
return None
return soup複製代碼
parse_page()
函數會不斷地發送請求到詳情頁連接,解析詳情頁的回調函數須要處理數據封裝到Item
,還須要提取詳情頁中更多圖片的詳情連接而後發送請求。
def parse_detail_page(self, response):
if response.url in self.filter:
self.logger.debug('[REPETITION] already parse url %s ' % response.url)
return None
soup = self._init_soup(response, '[PREPARING DETAIL PAGE]')
if soup is None:
return None
# 包裝Item並返回
yield self.packing_item(response.meta['item'], soup)
self.filter.add(response.url)
# 繼續抓取當前頁中的其餘圖片
all_div_tag = soup.find_all('div', class_='tt-crop thumb')
if all_div_tag is not None and len(all_div_tag) > 0:
for div_tag in all_div_tag:
detail_link = div_tag.find('a')['href']
request = Request(
url=detail_link,
headers=self.headers,
callback=self.parse_detail_page
)
request.meta['item'] = DeviantArtSpiderItem()
yield request
else:
self.logger.debug('[PARSE FAILED] get <div> tag failed')
return None
# 封裝數據到Item
def packing_item(self, item, soup):
self.logger.debug('[PREPARING PACKING ITEM]..........')
img = soup.find('img', class_='dev-content-full')
img_alt = img['alt'] # alt屬性中保存了圖片名與做者名
item['image_name'] = img_alt[:img_alt.find('by') - 1]
item['author'] = img_alt[img_alt.find('by') + 2:]
item['image_id'] = img['data-embed-id'] # data-embed-id屬性保存了圖片id
item['image_src'] = img['src']
self.logger.debug('[PACKING ITEM FINISHED] %s ' % item)
return item複製代碼
對於Item
的處理,只是簡單地將圖片命名與下載到本地。我沒有使用多進程或者多線程,也沒有使用Scrapy
自帶的ImagePipeline
(自由度不高),有興趣的童鞋能夠本身選擇實現。
import requests
import threading
import os
from scrapy.exceptions import DropItem, CloseSpider
class DeviantArtSpiderPipeline(object):
def __init__(self, IMAGE_STORE, MAXIMUM_IMAGE_NUMBER):
if IMAGE_STORE is None or MAXIMUM_IMAGE_NUMBER is None:
raise CloseSpider('Pipeline load settings failed')
self.IMAGE_STORE = IMAGE_STORE
self.MAXIMUM_IMAGE_NUMBER = MAXIMUM_IMAGE_NUMBER
# 記錄當前下載的圖片數量
self.image_max_counter = 0
# 根據圖片數量建立文件夾,每1000張在一個文件夾中
self.dir_counter = 0
def process_item(self, item, spider):
if item is None:
raise DropItem('Item is null')
dir_path = self.make_dir()
# 拼接圖片名稱
image_final_name = item['image_name'] + '-' + item['image_id'] + '-by@' + item['author'] + '.jpg'
dest_path = os.path.join(dir_path, image_final_name)
self.download_image(item['image_src'], dest_path)
self.image_max_counter += 1
if self.image_max_counter >= self.MAXIMUM_IMAGE_NUMBER:
raise CloseSpider('Current downloaded image already equal maximum number')
return item
def make_dir(self):
print('[IMAGE_CURRENT NUMBER] %d ' % self.image_max_counter)
if self.image_max_counter % 1000 == 0:
self.dir_counter += 1
path = os.path.abspath(self.IMAGE_STORE)
path = os.path.join(path, 'crawl_images')
path = os.path.join(path, 'dir-' + str(self.dir_counter))
if not os.path.exists(path):
os.makedirs(path)
print('[CREATED DIR] %s ' % path)
return path
def download_image(self, src, dest):
print('[Thread %s] preparing download image.....' % threading.current_thread().name)
response = requests.get(src, timeout=2)
if response.status_code == 200:
with open(dest, 'wb') as f:
f.write(response.content)
print('[DOWNLOAD FINISHED] from %s to %s ' % (src, dest))
else:
raise DropItem('[Thread %s] request image src failed status code = %s'
% (threading.current_thread().name, response.status_code))
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
return cls(settings['IMAGES_STORE'], settings['MAXIMUM_IMAGE_NUMBER'])複製代碼
在settings.py
中註冊該Pipeline
ITEM_PIPELINES = {
'deviant_art_spider.pipelines.DeviantArtSpiderPipeline': 300,
}複製代碼
有些網站會有反爬蟲機制,爲了解決這個問題,每次請求都使用不一樣的IP
代理,有不少網站提供IP
代理服務,咱們須要寫一個爬蟲從雲代理中抓取它提供的免費IP
代理(免費IP
很不穩定,並且我用了代理以後反而各類請求失敗了Orz...)。
import os
import requests
from bs4 import BeautifulSoup
class ProxiesSpider(object):
def __init__(self, max_page_number=10):
self.seed = 'http://www.ip3366.net/free/'
self.max_page_number = max_page_number # 最大頁數
self.crawled_proxies = [] # 爬到的ip,每一個元素都是一個dict
self.verified_proxies = [] # 校驗過的ip
self.headers = {
'Accept': '*/*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/45.0.2454.101 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.8'
}
self.tocrawl_url = []
def crawl(self):
self.tocrawl_url.append(self.seed)
page_counter = 1
while self.tocrawl_url:
if page_counter > self.max_page_number:
break
url = self.tocrawl_url.pop()
body = requests.get(url=url, headers=self.headers, params={'page': page_counter}).content
soup = BeautifulSoup(body, 'lxml')
if soup is None:
print('PARSE PAGE FAILED.......')
continue
self.parse_page(soup)
print('Parse page %s done' % (url + '?page=' + str(page_counter)))
page_counter += 1
self.tocrawl_url.append(url)
self.verify_proxies()
self.download()
# 解析頁面並封裝
def parse_page(self, soup):
table = soup.find('table', class_='table table-bordered table-striped')
tr_list = table.tbody.find_all('tr')
for tr in tr_list:
ip = tr.contents[1].text
port = tr.contents[3].text
protocol = tr.contents[7].text.lower()
url = protocol + '://' + ip + ':' + port
self.crawled_proxies.append({url: protocol})
print('Add url %s to crawled_proxies' % url)
# 對ip進行校驗
def verify_proxies(self):
print('Start verify proxies.......')
while self.crawled_proxies:
self.verify_proxy(self.crawled_proxies.pop())
print('Verify proxies done.....')
def verify_proxy(self, proxy):
proxies = {}
for key in proxy:
proxies[str(proxy[key])] = key # requests的proxies的格式必須爲 協議 : 地址
try:
if requests.get('https://www.deviantart.com/', proxies=proxies, timeout=2).status_code == 200:
print('verify proxy success %s ' % proxies)
self.verified_proxies.append(proxy)
except:
print('verify proxy fail %s ' % proxies)
# 保存到文件中
def download(self):
current_path = os.getcwd()
parent_path = os.path.dirname(current_path)
with open(parent_path + '\proxies.txt', 'w') as f:
for proxy in self.verified_proxies:
for key in proxy.keys():
f.write(key + '\n')
if __name__ == '__main__':
spider = ProxiesSpider()
spider.crawl()複製代碼
獲得了IP
代理池以後,還要在Scrapy
的middlewares.py
模塊定義代理中間件類。
import time
from scrapy import signals
import os
import random
class ProxyMiddleware(object):
# 每次請求前從IP代理池中選擇一個IP代理並進行設置
def process_request(self, request, spider):
proxy = self.get_proxy(self.make_path())
print('Acquire proxy %s ' % proxy)
request.meta['proxy'] = proxy
# 請求失敗,從新設置IP代理
def process_response(self, request, response, spider):
if response.status != 200:
proxy = self.get_proxy(self.make_path())
print('Response status code is not 200,try reset request proxy %s ' % proxy)
request.meta['proxy'] = proxy
return request
return response
def make_path(self):
current = os.path.abspath('.')
parent = os.path.dirname(current)
return os.path.dirname(parent) + '\proxies.txt'
# 從IP代理文件中隨機得到一個IP代理地址
def get_proxy(self, path):
if not os.path.isfile(path):
print('[LOADING PROXY] loading proxies failed proxies file is not exist')
while True:
with open(path, 'r') as f:
proxies = f.readlines()
if proxies:
break
else:
time.sleep(1)
return random.choice(proxies).strip()複製代碼
最後在settings.py
中進行註冊。
DOWNLOADER_MIDDLEWARES = {
# 這個中間件是由scrapy提供的,而且它是必需的
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 543,
# 咱們自定義的代理中間件
'deviant_art_spider.middlewares.ProxyMiddleware': 540
}複製代碼
咱們的圖片爬蟲已經完成了,執行命令scrapy crawl deviant_art_image_spider
,而後盡情蒐集圖片吧!
想要得到本文中的完整源代碼與P站爬蟲請點我,順便求個star...
最近心血來潮想要寫爬蟲,因此花了點時間過了一遍
python
語法便匆匆上手了,代碼寫的有點醜也不夠pythonic,各位看官求請吐槽。