在本篇中,我假定您已經熟悉並安裝了 Python3。 如若否則,請參考 Python 入門指南。css
Scrapy 是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 能夠應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。html
其最初是爲了 網絡抓取 所設計的, 也能夠應用在獲取 API 所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。python
Redis 3.2.5 Python 3.5.2 Scrapy 1.3.3 scrapy-redis 0.6.8 redis-py 2.10.5 PyMySQL 0.7.10 SQLAlchemy 1.1.6
安裝前你可能須要把 Python3 設置爲默認的 Python 解釋器,或者使用 virtualenv 搭建一個 Python 的虛擬環境,篇幅有限,此處再也不贅述。mysql
sudo apt-get install redis-server
sudo apt-get install build-essential libssl-dev libffi-dev python-dev sudo apt install python3-pip sudo pip install scrapy scrapy-reids
sudo pip install scrapy-reids
因爲目前 Python 實現的一部分第三方模塊在 Windows 下並無可用的安裝包,我的並不推薦以 Windows 做爲開發環境。正則表達式
若是你非要這麼作,你可能會遇到如下異常:redis
若是你尚未放棄,如下內容可能會幫到你:算法
scrapy startproject spider_ebay
└── spider_ebay ├── spider_ebay │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ └── __init__.py └── scrapy.cfg
spider_ebay/spider_ebay/spiders/example.py
from scrapy.spiders import Spider class ExampleSpider(Spider): name = 'example' start_urls = ['http://www.ebay.com/sch/allcategories/all-categories'] def parse(self, response): datas = response.xpath("//div[@class='gcma']/ul/li/a[@class='ch']") for data in datas: try: yield { 'name': data.xpath("text()").extract_first(), 'link': data.xpath("@href").extract_first() } # or # yield self.make_requests_from_url(data.xpath("@href").extract_first()) except: pass
該例爬取了 eBay 商品分類頁面下的子分類頁的 url 信息sql
ExampleSpider
繼承自 Spider
,定義了 name
、start_urls
屬性與 parse
方法。
程序經過 name
來調用爬蟲,爬蟲運行時會先從 strart_urls
中提取 url 構造 request
,獲取到對應的 response
時,
利用 parse
方法解析 response
,最後將目標數據或新的 request
經過 yield
語句以生成器的形式返回。數據庫
cd spider_ebay
scrapy crawl example -o items.json
spider_ebay/items.json
[ {"name": "Antiquities", "link": "http://www.ebay.com/sch/Antiquities/37903/i.html"}, {"name": "Architectural & Garden", "link": "http://www.ebay.com/sch/Architectural-Garden/4707/i.html"}, {"name": "Asian Antiques", "link": "http://www.ebay.com/sch/Asian-Antiques/20082/i.html"}, {"name": "Decorative Arts", "link": "http://www.ebay.com/sch/Decorative-Arts/20086/i.html"}, {"name": "Ethnographic", "link": "http://www.ebay.com/sch/Ethnographic/2207/i.html"}, {"name": "Home & Hearth", "link": "http://www.ebay.com/sch/Home-Hearth/163008/i.html"}, {"name": "Incunabula", "link": "http://www.ebay.com/sch/Incunabula/22422/i.html"}, {"name": "Linens & Textiles (Pre-1930)", "link": "http://www.ebay.com/sch/Linens-Textiles-Pre-1930/181677/i.html"}, {"name": "Manuscripts", "link": "http://www.ebay.com/sch/Manuscripts/23048/i.html"}, {"name": "Maps, Atlases & Globes", "link": "http://www.ebay.com/sch/Maps-Atlases-Globes/37958/i.html"}, {"name": "Maritime", "link": "http://www.ebay.com/sch/Maritime/37965/i.html"}, {"name": "Mercantile, Trades & Factories", "link": "http://www.ebay.com/sch/Mercantile-Trades-Factories/163091/i.html"}, {"name": "Musical Instruments (Pre-1930)", "link": "http://www.ebay.com/sch/Musical-Instruments-Pre-1930/181726/i.html"}, {"name": "Other Antiques", "link": "http://www.ebay.com/sch/Other-Antiques/12/i.html"}, {"name": "Periods & Styles", "link": "http://www.ebay.com/sch/Periods-Styles/100927/i.html"}, {"name": "Primitives", "link": "http://www.ebay.com/sch/Primitives/1217/i.html"}, {"name": "Reproduction Antiques", "link": "http://www.ebay.com/sch/Reproduction-Antiques/22608/i.html"}, {"name": "Restoration & Care", "link": "http://www.ebay.com/sch/Restoration-Care/163101/i.html"}, {"name": "Rugs & Carpets", "link": "http://www.ebay.com/sch/Rugs-Carpets/37978/i.html"}, {"name": "Science & Medicine (Pre-1930)", "link": "http://www.ebay.com/sch/Science-Medicine-Pre-1930/20094/i.html"}, {"name": "Sewing (Pre-1930)", "link": "http://www.ebay.com/sch/Sewing-Pre-1930/156323/i.html"}, {"name": "Silver", "link": "http://www.ebay.com/sch/Silver/20096/i.html"}, {"name": "Art from Dealers & Resellers", "link": "http://www.ebay.com/sch/Art-from-Dealers-Resellers/158658/i.html"}, {"name": "Direct from the Artist", "link": "http://www.ebay.com/sch/Direct-from-the-Artist/60435/i.html"}, {"name": "Baby Gear", "link": "http://www.ebay.com/sch/Baby-Gear/100223/i.html"}, {"name": "Baby Safety & Health", "link": "http://www.ebay.com/sch/Baby-Safety-Health/20433/i.html"}, {"name": "Bathing & Grooming", "link": "http://www.ebay.com/sch/Bathing-Grooming/20394/i.html"}, {"name": "Car Safety Seats", "link": "http://www.ebay.com/sch/Car-Safety-Seats/66692/i.html"}, {"name": "Carriers, Slings & Backpacks", "link": "http://www.ebay.com/sch/Carriers-Slings-Backpacks/100982/i.html"}, {"name": "Diapering", "link": "http://www.ebay.com/sch/Diapering/45455/i.html"}, {"name": "Feeding", "link": "http://www.ebay.com/sch/Feeding/20400/i.html"}, {"name": "Keepsakes & Baby Announcements", "link": "http://www.ebay.com/sch/Keepsakes-Baby-Announcements/117388/i.html"}, ......
MasterSpider
對 start_urls
中的 urls 構造 request
,獲取 response
MasterSpider
將 response
解析,獲取目標頁面的 url, 利用 redis 對 url 去重並生成待爬 request
隊列SlaveSpider
讀取 redis 中的待爬隊列,構造 request
SlaveSpider
發起請求,獲取目標頁面的 response
Slavespider
解析 response
,獲取目標數據,寫入生產數據庫Redis 是目前公認的速度最快的基於內存的鍵值對數據庫express
Redis 做爲臨時數據的緩存區,能夠充分利用內存的高速讀寫能力大大提升爬蟲爬取效率。
scrapy-redis 是爲了更方便地實現 Scrapy 分佈式爬取,而提供的一些以 Redis 爲基礎的組件。
scrapy 使用 python 自帶的
collection.deque
來存放待爬取的request
。scrapy-redis 提供了一個解決方案,把 deque 換成 redis 數據庫,能讓多個 spider 讀取同一個 redis 數據庫裏,解決了分佈式的主要問題。
使用 scrapy-redis 組件前須要對 Scrapy 配置作一些調整
spider_ebay/settings.py
# 過濾器 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 調度狀態持久化 SCHEDULER_PERSIST = True # 請求調度使用優先隊列 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # redis 使用的端口和地址 REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379
併發是指同時處理數量。其有全侷限制和局部(每一個網站)的限制。
Scrapy 默認的全局併發限制對同時爬取大量網站的狀況並不適用。 增長多少取決於爬蟲能佔用多少 CPU。 通常開始能夠設置爲 100 。
不過最好的方式是作一些測試,得到 Scrapy 進程佔取 CPU 與併發數的關係。 爲了優化性能,應該選擇一個能使CPU佔用率在80%-90%的併發數。
增長全局併發數的一些配置:
# 默認 Item 併發數:100 CONCURRENT_ITEMS = 100 # 默認 Request 併發數:16 CONCURRENT_REQUESTS = 16 # 默認每一個域名的併發數:8 CONCURRENT_REQUESTS_PER_DOMAIN = 8 # 每一個IP的最大併發數:0表示忽略 CONCURRENT_REQUESTS_PER_IP = 0
scrapy默認已經自帶了緩存,配置以下
# 打開緩存 HTTPCACHE_ENABLED = True # 設置緩存過時時間(單位:秒) #HTTPCACHE_EXPIRATION_SECS = 0 # 緩存路徑(默認爲:.scrapy/httpcache) HTTPCACHE_DIR = 'httpcache' # 忽略的狀態碼 HTTPCACHE_IGNORE_HTTP_CODES = [] # 緩存模式(文件緩存) HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
安裝完成後,redis默認是不能被遠程鏈接的,此時要修改配置文件/etc/redis.conf
# bind 127.0.0.1
修改後,重啓redis服務器
systemctl restart redis
若是要增長redis的訪問密碼,修改配置文件/etc/redis.conf
requirepass passwrd
增長了密碼後,啓動客戶端的命令變爲:
redis-cli -a passwrd
使用 windows 的命令窗口進入 redis 安裝目錄,用命令進行遠程鏈接 redis:
redis-cli -h 192.168.1.112 -p 6379
在本機上測試是否能讀取 master 的 redis
在遠程機器上讀取是否有該數據
# coding: utf-8 from scrapy import Item, Field from scrapy.spiders import Rule from scrapy_redis.spiders import RedisCrawlSpider from scrapy.linkextractors import LinkExtractor from redis import Redis from time import time from urllib.parse import urlparse, parse_qs, urlencode class MasterSpider(RedisCrawlSpider): name = 'ebay_master' redis_key = 'ebay:start_urls' ebay_main_lx = LinkExtractor(allow=(r'http://www.ebay.com/sch/allcategories/all-categories', )) ebay_category2_lx = LinkExtractor(allow=(r'http://www.ebay.com/sch/[^\s]*/\d+/i.html', r'http://www.ebay.com/sch/[^\s]*/\d+/i.html?_ipg=\d+&_pgn=\d+', r'http://www.ebay.com/sch/[^\s]*/\d+/i.html?_pgn=\d+&_ipg=\d+',)) rules = ( Rule(ebay_category2_lx, callback='parse_category2', follow=False), Rule(ebay_main_lx, callback='parse_main', follow=False), ) def __init__(self, *args, **kwargs): domain = kwargs.pop('domain', '') # self.allowed_domains = filter(None, domain.split(',')) super(MasterSpider, self).__init__(*args, **kwargs) def parse_main(self, response): pass data = response.xpath("//div[@class='gcma']/ul/li/a[@class='ch']") for d in data: try: item = LinkItem() item['name'] = d.xpath("text()").extract_first() item['link'] = d.xpath("@href").extract_first() yield self.make_requests_from_url(item['link'] + r"?_fsrp=1&_pppn=r1&scp=ce2") except: pass def parse_category2(self, response): data = response.xpath("//ul[@id='ListViewInner']/li/h3[@class='lvtitle']/a[@class='vip']") redis = Redis() for d in data: # item = LinkItem() try: self._filter_url(redis, d.xpath("@href").extract_first()) except: pass try: next_page = response.xpath("//a[@class='gspr next']/@href").extract_first() except: pass else: # yield self.make_requests_from_url(next_page) new_url = self._build_url(response.url) redis.lpush("test:new_url", new_url) # yield self.make_requests_from_url(new_url) # yield Request(url, headers=self.headers, callback=self.parse2) def _filter_url(self, redis, url, key="ebay_slave:start_urls"): is_new_url = bool(redis.pfadd(key + "_filter", url)) if is_new_url: redis.lpush(key, url) def _build_url(self, url): parse = urlparse(url) query = parse_qs(parse.query) base = parse.scheme + '://' + parse.netloc + parse.path if '_ipg' not in query.keys() or '_pgn' not in query.keys() or '_skc' in query.keys(): new_url = base + "?" + urlencode({"_ipg": "200", "_pgn": "1"}) else: new_url = base + "?" + urlencode({"_ipg": query['_ipg'][0], "_pgn": int(query['_pgn'][0]) + 1}) return new_url class LinkItem(Item): name = Field() link = Field()
MasterSpider
繼承來自 scrapy-redis 組件下的 RedisCrawlSpider
,相比 ExampleSpider
有了如下變化:
redis_key
start_urls
的存放容器由原先的 Python list 改至 redis list,因此此處須要 redis_key
存放 redis list 的 keyrules
rules
是含有多個 Rule
對象的 tupleRule
對象實例化經常使用的三個參數:link_extractor
/ callback
/ follow
link_extractor
是一個 LinkExtractor
對象。 其定義瞭如何從爬取到的頁面提取連接callback
是一個 callable 或 string (該spider中同名的函數將會被調用)。 從 link_extractor中每獲取到連接時將會調用該函數。該回調函數接受一個response做爲其第一個參數, 並返回一個包含 Item 以及(或) Request 對象(或者這二者的子類)的列表(list)。follow
是一個布爾(boolean)值,指定了根據該規則從response提取的連接是否須要跟進。 若是 callback 爲None, follow 默認設置爲 True ,不然默認爲 False 。process_links
處理全部的連接的回調,用於處理從response提取的links,一般用於過濾(參數爲link列表)process_request
連接請求預處理(添加header或cookie等)ebay_main_lx
/ ebay_category2_lx
LinkExtractor
對象
allow
(a regular expression (or list of)) – 必需要匹配這個正則表達式(或正則表達式列表)的URL纔會被提取。若是沒有給出(或爲空), 它會匹配全部的連接。deny
排除正則表達式匹配的連接(優先級高於allow)allow_domains
容許的域名(能夠是str或list)deny_domains
排除的域名(能夠是str或list)restrict_xpaths
: 取知足XPath選擇條件的連接(能夠是str或list)restrict_css
提取知足css選擇條件的連接(能夠是str或list)tags
提取指定標籤下的連接,默認從a和area中提取(能夠是str或list)attrs
提取知足擁有屬性的連接,默認爲href(類型爲list)unique
連接是否去重(類型爲boolean)process_value
值處理函數(優先級大於allow)parse_main
/ parse_category2
_filter_url
/ _build_url
LinkItem
# coding: utf-8 from scrapy import Item, Field from scrapy_redis.spiders import RedisSpider class SlaveSpider(RedisSpider): name = "ebay_slave" redis_key = "ebay_slave:start_urls" def parse(self, response): item = ProductItem() item["price"] = response.xpath("//span[contains(@id,'prcIsum')]/text()").extract_first() item["item_id"] = response.xpath("//div[@id='descItemNumber']/text()").extract_first() item["seller_name"] = response.xpath("//span[@class='mbg-nw']/text()").extract_first() item["sold"] = response.xpath("//span[@class='vi-qtyS vi-bboxrev-dsplblk vi-qty-vert-algn vi-qty-pur-lnk']/a/text()").extract_first() item["cat_1"] = response.xpath("//li[@class='bc-w'][1]/a/span/text()").extract_first() item["cat_2"] = response.xpath("//li[@class='bc-w'][2]/a/span/text()").extract_first() item["cat_3"] = response.xpath("//li[@class='bc-w'][3]/a/span/text()").extract_first() item["cat_4"] = response.xpath("//li[@class='bc-w'][4]/a/span/text()").extract_first() yield item class ProductItem(Item): name = Field() price = Field() sold = Field() seller_name = Field() pl_id = Field() cat_id = Field() cat_1 = Field() cat_2 = Field() cat_3 = Field() cat_4 = Field() item_id = Field()
SlaveSpider
繼承自 RedisSpider
,屬性與方法相比 MasterSpider
簡單了很多,少了 rules
與其餘,但大體功能都比較相似SlaveSpider
從 ebay_slave:start_urls
下讀取構建好的目標頁面的 request,對 response 解析出目標數據,以 ProductItem
的形式輸出數據
scrpay-redis 默認狀況下會將爬取到的目標數據寫入 redis
利用 Python 豐富的數據庫接口支持能夠經過 Pipeline 把 Item 中的數據存放在任意一種常見的數據庫中
SQLAlchemy 是在Python中最有名的 ORM 框架。經過 SQLAlchemy 你能夠用操做對象的方式來操做 mysql,sqlite,sqlserver,oracle 等大部分常見數據庫
安裝
pip install pymysql
pip install sqlalchemy
ebay_spider/settings.py
ITEM_PIPELINES = { 'ebay_spider.pipelines.ExamplePipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400, }
咱們在 settings.py 模塊中配置 ebay_spider.pipelines.ExamplePipeline
把 ExamplePipeline 配置到爬蟲上,後面的數字 300 表示 pipeline 的執行順序,數值小的先執行scrapy_redis.pipelines.RedisPipeline
是 scrapy-redis 使用的默認的 pipeline,若是不須要 redis 保存目標數據,能夠不配置
ebay_spider/pipelines.py
from scrapy.exceptions import DropItem from sqlalchemy import create_engine from .model.config import DBSession from .model.transfer import Transfer class ExamplePipeline(object): def open_spider(self, spider): self.session = DBSession() self.session.execute('SET NAMES utf8;') self.session.execute('SET CHARACTER SET utf8;') self.session.execute('SET character_set_connection=utf8;') def process_item(self, item, spider): a = Transfer( transfer_order_id = item['session_online_id'], transfer_content = item['session_name'].encode('utf8') ) self.session.merge(a) self.session.commit() return item def close_spider(self, spider): self.session.close()
此處定義把數據保存到 Mysql 的 ExamplePipeline,
其中,pipeline 的 open_spider 和 spider_closed 兩個方法,在爬蟲啓動和關閉的時候調用
此 pipeline 在爬蟲啓動時,創建起與 Mysql 的鏈接。當 spider 輸出 Item 時將 Item 中的數據存入 Mysql 中。在爬蟲關閉的同時,關閉與數據庫的鏈接
ebay_spider/models/config.py
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("mysql+pymysql://root:12345678@localhost/beston") DBSession = sessionmaker(bind=engine)
這是 ExamplePipeline 中使用到的數據庫鏈接配置。要注意的是,此處使用的是 pymysql 做爲數據庫驅動,而不是 MySQLdb。
ebay_spider/models/transfer.py
# coding:utf8 from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Transfer(Base): # 表名 __tablename__ = 'bt_transfer' __table_args__ = { 'mysql_engine': 'MyISAM', 'mysql_charset': 'utf8' } # 表結構 transfer_id = Column(Integer, primary_key=True) transfer_order_id = Column(Integer) transfer_content = Column(String(255))
以上是 Mysql ORM 模型,定義了 bt_transfer 表。也可以使用 SQLAlchemy 的命令來生成此表。
大多網站對爬蟲的活動都進行了限制,anti-anti-spider 即 反反爬蟲
。是爲了突破這些限制的一些解決方案的稱呼。
如下介紹幾種經常使用的方案
經過僞造 request header 中的 User-Agent 能夠模仿瀏覽器操做,從而繞過一些網站的反爬蟲機制
user_agent.py
agents = [ "Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus S Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", "Avant Browser/1.2.789rel1 (http://www.avantbrowser.com)", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", "Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB5", "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)", "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110622 Firefox/6.0a2", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b4pre) Gecko/20100815 Minefield/4.0b4pre", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0 )", "Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90)", "Mozilla/5.0 (Windows; U; Windows XP) Gecko MultiZilla/1.6.1.0a", "Mozilla/2.02E (Win95; U)", "Mozilla/3.01Gold (Win95; I)", "Mozilla/4.8 [en] (Windows NT 5.1; U)", "Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.4) Gecko Netscape/7.1 (ax)", "HTC_Dream Mozilla/5.0 (Linux; U; Android 1.5; en-ca; Build/CUPCAKE) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.2; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.40.1 Safari/534.6 TouchPad/1.0", "Mozilla/5.0 (Linux; U; Android 1.5; en-us; sdk Build/CUPCAKE) AppleWebkit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", "Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17", "Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", "Mozilla/5.0 (Linux; U; Android 1.5; en-us; htc_bahamas Build/CRB17) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", "Mozilla/5.0 (Linux; U; Android 2.1-update1; de-de; HTC Desire 1.19.161.5 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17", ...... ]
UserAgentMiddleware
import random from .user_agent import agents from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware class UserAgentmiddleware(UserAgentMiddleware): def process_request(self, request, spider): agent = random.choice(agents) request.headers["User-Agent"] = agent
UserAgentmiddleware
定義了函數 process_request(request, spider),Scrapy 每個 request 經過中間件都會隨機的從 user_agent.py
中獲取一個僞造的 User-Agent 放入 request 的 header,來達到欺騙的目的。
反爬蟲一個最經常使用的方法的就是限制 ip。爲了不最壞的狀況,能夠利用代理服務器來爬取數據,scrapy 設置代理服務器只須要在請求前設置 Request 對象的 meta 屬性,添加 proxy 值便可,
能夠經過中間件來實現:
class ProxyMiddleware(object): def process_request(self, request, spider): proxy = 'https://178.33.6.236:3128' # 代理服務器 request.meta['proxy'] = proxy
另外,也可使用大量的 IP Proxy 創建起代理 IP 池,請求時隨機調用來避免更嚴苛的 IP 限制機制,方法相似 User-Agent 池
正常業務邏輯下,爬蟲不會對重複爬取同一個頁面兩次。因此爬蟲默認都會對重複請求進行過濾,但當爬蟲體量達到千萬級時,默認的過濾器佔用的內存將會遠遠超乎你的想象。
爲了解決這個問題,能夠經過一些算法來犧牲一點點過濾的準確性來換取更小的空間複雜度
Bloom Filter能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。
HyperLogLog是一個基數估計算法。其空間效率很是高,1.5K內存能夠在偏差不超過2%的前提下,用於超過10億的數據集合基數估計。
這兩種算法都是合適的選擇,以 Hyperloglog 爲例
因爲 redis 已經提供了支持 hyperloglog 的數據結構,因此只需對此數據結構進行操做便可
MasterSpider 下的 _filter_url
實現了過濾 URL 的功能
def _filter_url(self, redis, url, key="ebay_slave:start_urls"): is_new_url = bool(redis.pfadd(key + "_filter", url)) if is_new_url: redis.lpush(key, url)
當 redis.pfadd()
執行時,一個 url 嘗試插入 hyperloglog 結構中,若是 url 存在返回 0,反之返回 1。由此來判斷是否要將該 url 存放至待爬隊列
Scrapy 是一個優秀的爬蟲框架。性能上,它快速強大,多線程併發與事件驅動的設計能將爬取效率提升幾個數量級;功能上,它又極易擴展,支持插件,無需改動核心代碼。但若是要運用在在大型爬蟲項目中,不支持分佈式設計是它的一個大硬傷。幸運的是,scrapy-redis 組件解決了這個問題,並給 Scrapy 帶來了更多的可能性。
Scrapy 1.0 文檔
Scrapy-Redis’s documentation
使用SQLAlchemy
布隆過濾器
HyperLogLog
Python 入門指南
scrapy_redis去重優化
基於Scrapy-Redis的分佈式以及cookies池
本文摘自(特別感謝分享):https://www.jianshu.com/p/cd4054bbc757/