前言
在之前的文章Scrapy學(xué)習(xí)筆記(2)-使用pycharm在虛擬環(huán)境中運(yùn)行第一個(gè)spider中有提到在使用scrapy genspider命令生成spider的時(shí)候可以使用-t參數(shù)來指定生成spider的模板桩警,前面幾篇文章中我們沒有指定模板晕拆,所以都是使用最基本的Spider類來爬取數(shù)據(jù)胸哥。Spider其實(shí)能做很多事情了,但是如果你想對(duì)某個(gè)網(wǎng)站進(jìn)行全站爬取的話赴魁,你可能需要一個(gè)更強(qiáng)大的武器—CrawlSpider卸奉。另外之前的文章中我們?nèi)绻雅廊〉臄?shù)據(jù)保存到數(shù)據(jù)庫,那我們就得提前在庫中執(zhí)行DDL語句將表建好颖御,有時(shí)候覺得設(shè)計(jì)表并寫SQL是個(gè)很麻煩的事情榄棵,就想著能不能省掉寫SQL的過程,查了下資料還真有潘拱,那就是使用sqlalchemy疹鳄,本文記錄實(shí)戰(zhàn)過程。
基礎(chǔ)知識(shí)
class scrapy.spiders.CrawlSpider
這是抓取一般網(wǎng)頁最常用的類芦岂,除了從Spider繼承過來的屬性外瘪弓,其提供了一個(gè)新的屬性rules,它提供了一種簡(jiǎn)單的機(jī)制禽最,能夠?yàn)閷⒁廊〉逆溄佣x一組提取規(guī)則腺怯。
rules
這是一個(gè)Rule對(duì)象列表袱饭,每條規(guī)則定義了爬取網(wǎng)站鏈接的行為,如果一條鏈接命中多條規(guī)則呛占,以第一條規(guī)則進(jìn)行匹配虑乖,順序由屬性中定義的順序決定。
Link Extractors
Link Extractors 是用于從網(wǎng)頁(scrapy.http.Response)中抽取會(huì)被follow鏈接的對(duì)象晾虑。 Scrapy 自帶的Link Extractors類由scrapy.linkextractors模塊提供疹味,你可以這樣直接導(dǎo)入from scrapy.linkextractors?import?LinkExtractor,也可以通過實(shí)現(xiàn)一個(gè)簡(jiǎn)單的接口來創(chuàng)建自己個(gè)性化的Link Extractor來滿足需求走贪。每個(gè)LinkExtractor都有唯一的公共方法是?extract_links?佛猛,其接收 一個(gè)Response對(duì)象, 并返回scrapy.link.Link?對(duì)象? Link Extractors只實(shí)例化一次坠狡,其?extract_links?方法會(huì)根據(jù)不同的response被調(diào)用多次來提取鏈接?默認(rèn)的link extractor 是?LinkExtractor?,其實(shí)就是?LxmlLinkExtractor遂跟,在以前版本的Scrapy版本中還提供了其他的link extractor逃沿,不過都已經(jīng)被廢棄了。
LxmlLinkExtractor
classscrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(
allow=(),
deny=(),
allow_domains=(),
deny_domains=(),
deny_extensions=None,
restrict_xpaths=(),
restrict_css=(),
tags=(‘a(chǎn)’,?‘a(chǎn)rea’),
attrs=(‘href’,?),
canonicalize=True,
unique=True,
process_value=None
)
參數(shù)解釋:
allow 只有匹配這個(gè)正則表達(dá)式(或正則表達(dá)式列表)的URL才會(huì)被提取?如果沒有給出(或None) ,它會(huì)匹配所有的鏈接?
deny 匹配這個(gè)正則表達(dá)式(或正則表達(dá)式列表)的URL將會(huì)被排除在外(即不提取)?它的優(yōu)先級(jí)高于allow參數(shù)幻锁,如果沒有給出(或None) ,將不排除任何鏈接?
allow_domains 包含特定域名的字符串或字符串列表凯亮,表示允許從這里面提取鏈接
deny_domains 包含特定域名的字符串或字符串列表, 表示不允許從這里面提取鏈接
deny_extensions 提取鏈接時(shí),忽略擴(kuò)展名的列表?如果沒有給出,默認(rèn)為scrapy.linkextractor模塊中定義的ignored_extensions列表?
restrict_xpaths 單個(gè)xpath表達(dá)式或xpath表達(dá)式列表,若不為空,則只使用該參數(shù)去提取URL哄尔,和allow共同作用過濾鏈接假消。
restrict_css 單個(gè)css選擇器或者選擇器列表,作用和restrict_xpaths一樣
tags 提取鏈接時(shí)要考慮的標(biāo)簽或標(biāo)簽列表?默認(rèn)為?(?‘a(chǎn)’?,?‘a(chǎn)rea’)
attrs?提取鏈接時(shí)應(yīng)該尋找的attrbitues列表(僅在?tags?參數(shù)中指定的標(biāo)簽)?默認(rèn)為?(‘href’)?
canonicalize 是否標(biāo)準(zhǔn)化每個(gè)提取的URL岭接,使用w3lib.url.canonicalize_url富拗。默認(rèn)為True。
unique 是否過濾提取過的URL鸣戴,布爾類型
process_value 處理tags和attrs提取到的URL的函數(shù)啃沪,它能修改并返回一個(gè)新值。如果為空則默認(rèn)是lambda?x:?x
Rule
classscrapy.spiders.Rule(
link_extractor,
callback=None,
cb_kwargs=None,
follow=None,
process_links=None,
process_request=None
)
參數(shù)解釋:
link_extractor 是一個(gè)Link Extractor對(duì)象窄锅,定義怎樣提取每個(gè)需要爬取的頁面中的鏈接创千。
callback 是一個(gè)可調(diào)用方法或者一個(gè)字符串(spider類中用這個(gè)字符串命名的方法)會(huì)被每個(gè)指定的link_extractor 調(diào)用,這個(gè)方法的第一個(gè)參數(shù)是response必須返回一個(gè)item或者Request的list入偷。
cb_kwargs 是一個(gè)包含關(guān)鍵字參數(shù)的字典追驴,可以傳遞給callback函數(shù)。
follow 是一個(gè)布爾值疏之,指定這些通過規(guī)則匹配出來的鏈接是否需要繼續(xù)殿雪,如果callback是None,follow默認(rèn)為False体捏,否則follow是True冠摄。
process_links是一個(gè)可調(diào)用方法或者一個(gè)字符串(spider類中用這個(gè)字符串命名的方法)會(huì)被每個(gè)指定的link_extractor調(diào)用糯崎,這個(gè)主要作用是過濾。
process_request 是一個(gè)可調(diào)用方法或者一個(gè)字符串(spider類中用這個(gè)字符串命名的方法)會(huì)被這個(gè)規(guī)則的每個(gè)request調(diào)用河泳,必須返回一個(gè)request或者None沃呢。
SQLAlchemy
是python的一款開源軟件,提供了SQL工具包及對(duì)象關(guān)系映射(ORM)工具(需要安裝第三方庫)拆挥。它的優(yōu)點(diǎn)用一句話概括就是可以避免寫繁復(fù)的sql語句.(隱藏?cái)?shù)據(jù)庫薄霜,良好的數(shù)據(jù)接口,動(dòng)態(tài)的數(shù)據(jù)映射纸兔,引入緩存)具體請(qǐng)參考官方文檔
Xpath
scrapy支持使用xpath表達(dá)式來提取數(shù)據(jù)惰瓜。XPath即為XML路徑語言(XML?Path Language),它是一種用來確定XML文檔中某部分位置的語言汉矿。 XPath基于XML的樹狀結(jié)構(gòu)崎坊,提供在數(shù)據(jù)結(jié)構(gòu)樹中找尋節(jié)點(diǎn)的能力。起初XPath的提出的初衷是將其作為一個(gè)通用的洲拇、介于XPointer與XSL間的語法模型奈揍。但是XPath很快的被開發(fā)者采用來當(dāng)作小型查詢語言。具體可參考http://www.runoob.com/xpath/xpath-tutorial.html
實(shí)踐
有了前面的知識(shí)和基本概念之后赋续,下面就是寫代碼了男翰,本文目標(biāo)是使用CrawlSpider和sqlalchemy實(shí)現(xiàn)如下網(wǎng)站中的高匿代理IP采集入庫http://ip84.com,新建項(xiàng)目和spider的過程我就不寫了纽乱,不會(huì)的可以參考之前的文章蛾绎,本次項(xiàng)目名稱為”ip_proxy_pool”,顧名思義就是IP代理池鸦列,學(xué)習(xí)爬蟲的應(yīng)該都知道租冠,不過本文僅僅是采集特定網(wǎng)站公開的代理IP,維護(hù)一個(gè)IP代理池那是后話敛熬,OK肺稀,Talk is cheap,Show you the code应民!
項(xiàng)目結(jié)構(gòu)如上圖所示话原,model目錄存放數(shù)據(jù)庫表的映射文件,proxy.py是目標(biāo)表的映射文件诲锹,rules.py以及和model目錄同級(jí)的__init__.py文件本文中暫時(shí)用不到先不管繁仁,其他文件都是本次實(shí)踐需要用到的。
1.items.py
# -*- coding: utf-8 -*-# Define here the models for your scraped items## See documentation in:# http://doc.scrapy.org/en/latest/topics/items.htmlimport scrapyclass IpProxyPoolItem(scrapy.Item):? ? ip_port = scrapy.Field()? ? type = scrapy.Field()? ? level = scrapy.Field()? ? country = scrapy.Field()? ? location = scrapy.Field()? ? speed = scrapy.Field()? ? source = scrapy.Field()
2.model目錄下的__init__.py
# -*- coding: utf-8 -*-from sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy import create_enginefrom sqlalchemy.orm import sessionmaker# 創(chuàng)建對(duì)象的基類:Base = declarative_base()# 初始化數(shù)據(jù)庫連接:engine = create_engine('mysql+mysqldb://root:123456@localhost:3306/scrapy?charset=utf8')#返回?cái)?shù)據(jù)庫會(huì)話def loadSession():? ? Session = sessionmaker(bind=engine)? ? session = Session()? ? return session
3.proxy.py(數(shù)據(jù)庫表proxies的映射文件)
# -*- coding: utf-8 -*-from sqlalchemy import Column,String,Integer,DateTimefrom . import Baseimport datetimeclass Proxy(Base):? ? __tablename__ = 'proxies'? ? ip_port=Column(String(30),primary_key=True,nullable=False)? ? type=Column(String(20),nullable=True)? ? level=Column(String(20),nullable=True)? ? location=Column(String(100),nullable=True)? ? speed=Column(Integer,nullable=True)? ? source = Column(String(500), nullable=False)? ? indate=Column(DateTime,nullable=False)? ? def __init__(self,ip_port,source,type=None,level=None,location=None,speed=None):? ? ? ? self.ip_port=ip_port
? ? ? ? self.type=type
? ? ? ? self.level=level
? ? ? ? self.location=location
? ? ? ? self.speed=speed
? ? ? ? self.source=source
? ? ? ? self.indate=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
4.pipelines.py
# -*- coding: utf-8 -*-# Define your item pipelines herefrom model import Base,engine,loadSessionfrom model import proxyclass IpProxyPoolPipeline(object):? ? #搜索Base的所有子類归园,并在數(shù)據(jù)庫中生成表? ? Base.metadata.create_all(engine)? ? def process_item(self, item, spider):? ? ? ? a = proxy.Proxy(? ? ? ? ? ? ip_port=item['ip_port'],? ? ? ? ? ? type=item['type'],? ? ? ? ? ? level=item['level'],? ? ? ? ? ? location=item['location'],? ? ? ? ? ? speed=item['speed'],? ? ? ? ? ? source=item['source']? ? ? ? )? ? ? ? session = loadSession()? ? ? ? session.add(a)? ? ? ? session.commit()? ? ? ? return item
5.proxy_spider.py
# -*- coding: utf-8 -*-import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom ..items import IpProxyPoolItemclass ProxySpiderSpider(CrawlSpider):? ? name = 'proxy_spider'? ? allowed_domains = ['ip84.com']? ? start_urls = ['http://ip84.com/gn']? ? rules = (? ? ? ? #跟隨下一頁鏈接? ? ? ? Rule(LinkExtractor(restrict_xpaths="http://a[@class='next_page']"),follow=True),? ? ? ? #對(duì)所有鏈接中含有"/gn/數(shù)字"的鏈接調(diào)用parse_item函數(shù)進(jìn)行數(shù)據(jù)提取并過濾重復(fù)鏈接? ? ? ? Rule(LinkExtractor(allow=r'/gn/\d+',unique=True), callback='parse_item'),? ? )? ? def parse_item(self, response):? ? ? ? print 'Hi, this is an item page! %s' % response.url
? ? ? ? item=IpProxyPoolItem()? ? ? ? for proxy in response.xpath("http://table[@class='list']/tr[position()>1]"):? ? ? ? ? ? ip=proxy.xpath("td[1]/text()").extract_first()? ? ? ? ? ? port=proxy.xpath("td[2]/text()").extract_first()? ? ? ? ? ? location1=proxy.xpath("td[3]/a[1]/text()").extract_first()? ? ? ? ? ? location2=proxy.xpath("td[3]/a[2]/text()").extract_first()? ? ? ? ? ? level=proxy.xpath("td[4]/text()").extract_first()? ? ? ? ? ? type = proxy.xpath("td[5]/text()").extract_first()? ? ? ? ? ? speed=proxy.xpath("td[6]/text()").extract_first()? ? ? ? ? ? item['ip_port']=(ip if ip else "")+":"+(port if port else "")? ? ? ? ? ? item['type']=(type if type else "")? ? ? ? ? ? item['level']=(level if level else "")? ? ? ? ? ? item['location']=(location1 if location1 else "")+" "+(location2 if location2 else "")? ? ? ? ? ? item['speed']=(speed if speed else "")? ? ? ? ? ? item['source']=response.url
? ? ? ? ? ? return item
6.settings.py
# -*- coding: utf-8 -*-# Scrapy settings for ip_proxy_pool projectBOT_NAME = 'ip_proxy_pool'SPIDER_MODULES = ['ip_proxy_pool.spiders']NEWSPIDER_MODULE = 'ip_proxy_pool.spiders'# Obey robots.txt rulesROBOTSTXT_OBEY = TrueITEM_PIPELINES = {? 'ip_proxy_pool.pipelines.IpProxyPoolPipeline': 300,}DOWNLOAD_DELAY = 2
7.運(yùn)行spider黄虱,查看結(jié)果
shell中執(zhí)行scrapy crawl proxy_spider,發(fā)現(xiàn)數(shù)據(jù)庫中已經(jīng)自動(dòng)生成了表proxies并且數(shù)據(jù)已經(jīng)入庫,Done!