• <scrapy爬虫>Spiders的用法


    1、能够创建scrapy项目、编写个简单的蜘蛛并运行蜘蛛;
    2、能够简单的使用scrapy shell 调试数据;
    3、能够使用scrapy css选择器提取简单数据;
    4、除了能够提取一页数据,还要能提取下一页、在下一页。

    1、scrapy如何打开页面?

      蜘蛛从互联网的本质出发,我们浏览页面都是一个:发送请求、返回请求的过程,那蜘蛛要发送请求,那总得要有请求链接,因此引出了scrapy spiders的第一个必须的常量:start_urls

      URL有两种写法,一种作为类的常量、一种作为start_requests(self)方法的常量,无论哪一种写法,URL都是必须的!如果URL是定义在start_request(self)这个方法里面,那我们就要使用: yield scrapy.Request 方法发送请求:如下:

    import scrapy
    class simpleUrl(scrapy.Spider):
        name = "simpleUrl"
    
        # 另外一种初始链接写法
        def start_requests(self):
             urls = [ #爬取的链接由此方法通过下面链接爬取页面
                 'http://lab.scrapyd.cn/page/1/',
                 'http://lab.scrapyd.cn/page/2/',
             ]
             for url in urls:
                #发送请求
                 yield scrapy.Request(url=url, callback=self.parse)
    

      这样写的一个麻烦之处就是我们需要处理我们的返回,也就是我们还需要写一个callback方法来处理response;因此大多数我们都是把URL作为类的常量,然后再加上另外一个方法:parse(response)

      使用这个方法来发送请求,可以看到里面有个参数已经是:response(返回),也就是说这个方自动化的完成了:request(请求页面)-response(返回页面)的过程,我们就不必要再写函数接受返回,所以这样就比较方便了!

    import scrapy
    class simpleUrl(scrapy.Spider):
        name = "simpleUrl"
        start_urls = [  #另外一种写法,无需定义start_requests方法
            'http://lab.scrapyd.cn/page/1/',
            'http://lab.scrapyd.cn/page/2/',
        ]
    
        def parse(self, response):
            page = response.url.split("/")[-2]
            filename = 'mingyan-%s.html' % page
            with open(filename, 'wb') as f:
                f.write(response.body)
            self.log('保存文件: %s' % filename)
    

      好了,这就是scrapy打开页面的方法;页面打开后是不是我们就该提取数据了?那scrapy如何提取?

    2、scrapy如何提取数据?

      如何获取页面里面的内容?类比,人类如何做的呢?肯定是用眼睛查找!同理scrapy也有眼睛,分别是:css选择器、xpath选择器、正则,这三双眼睛实现的功能都一样:万军从中直取上将,说白了就是查找的功能!标签属性值提取、标签内容提取;

    一、scrapy xpath 属性提取 

    3、scrapy如何保存数据?

    scrapy命令明细:全局命令

    scrapy startproject(创建项目)、
    scrapy crawl XX(运行XX蜘蛛)、
    scrapy shell http://www.scrapyd.cn(调试网址为http://www.scrapyd.cn的网站),

    startproject
    
    genspider
    
    settings
    
    runspider
    
    shell
    
    fetch
    
    view
    
    version
    

      

    一、startproject

    这个是见得最多,创建项目的,如,创建一个名为:scrapyChina的项目:

    scrapy strartproject scrapychina

    用法灰常简单,你能看到这里,应该已经用过多次了,就这么简单;

    二、genspider

    这个命令是给我们创建蜘蛛模板的,example是蜘蛛名,example.com是start_urls,明白之后根据项目创建一个有针对性的,既然是爬淘宝,那我们就输入 :名称不能和项目相同

    scrapy genspider taobao taobao.com
    

    查看命令帮助

    scrapy genspider -h
    

      

    可以看到,scrapy genspider有如下格式:

     scrapy genspider [options] <name> <domain>
    

     <name>和<domain>上面已经使用过![options] 是神马呢,可以看到,也就是可以加如下几个参数:

    Options
    =======
    --help, -h              show this help message and exit
    --list, -l              List available templates
    --edit, -e              Edit spider after creating it
    --dump=TEMPLATE, -d TEMPLATE
                            Dump template to standard output
    --template=TEMPLATE, -t TEMPLATE
                            Uses a custom template.
    --force                 If the spider already exists, overwrite it with the
                            template
    

      

    详细帮助

    scrapy genspider -l 

    得到如下信息

    Available templates:
      basic
      crawl
      csvfeed
      xmlfeed
    

    这里的意思是可用的模板,那也就是说我们可以用上面的模板输出我们的蜘蛛文件,但是要结合下面的参数 -t 一起用,来,试一下:

    scrapy genspider -t crawl taobao2 taobao.com
    

      执行之后,你会发现,又给我们创建了一个名为:taobao2的蜘蛛,但是里面的蜘蛛格式是:crawl类型:

    三、settings

    它就是方便你查看到你对你的scray设置了些神马参数!比如我们想得到蜘蛛的下载延迟,我们可以使用:

    scrapy settings --get DOWNLOAD_DELAY 

    比如我们想得到蜘蛛的名字:

    scrapy settings --get BOT_NAME
    

    四、runspider

    运行蜘蛛除了使用:scrapy crawl XX之外,我们还能用:runspider,前者是基于项目运行,后者是基于文件运行,也就是说你按照scrapy的蜘蛛格式编写了一个py文件,那你不想创建项目,那你就可以使用runspider,比如你编写了一个:scrapyd_cn.py的蜘蛛,你要直接运行就是:

    scrapy runspider scrapy_cn.py
    

    五、shell

    主要是调试用,里面还有很多细节的命令,这里的话你需要记住它的使用方法,比如我们要调试http://www.scrapyd.cn

    scrapy shell http://www.scrapyd.cn
    

      

    然后我们可以直接执行命令,response,比如我们要测试我们获取标题的选择器正不正确,我们可以这样:

    response.css("title").extract_first()
    

    基本上,这就是这个命令最常用的用法,应该记住它就是调试用的

    六、fetch

    这个命令其实也可以归结为调试命令的范畴!它的功效就是模拟我们的蜘蛛下载页面,也就是说用这个命令下载的页面就是我们蜘蛛运行时下载的页面,这样的好处就是能准确诊断出,我们的到的html结构到底是不是我们所看到的,然后能及时调整我们编写爬虫的策略

    scrapy fetch http://www.scrapyd.cn
    

    就这样,如果你要把它下载的页面保存到一个html文件中进行分析,我们可以使用window或者linux的输出命令,这里演示window下如下如何把下载的页面保存:

    scrapy fetch http://www.scrapyd.cn >d:/3.html
    

    经过这个命令,scrapy下载的html文件已经被存储,接下来你就全文找找,看有木有那个节点,木有的话,毫无悬念,使用了异步加载!

    七、view

    和fetch类似都是查看蜘蛛看到的是否和你看到的一致,便于排错,用法:

    scrapy view http://www.scrapyd.cn
    

    八、version

    查看scrapy版本,用法:

    scrapy version
    

    scrapy 命令行:scrpay项目命令

    crawl
    check
    list
    edit
    parse
    bench
    

      

    1、crawl:运行蜘蛛

    例:我们要运行name = scrapyd_cn的蜘蛛,我们可以这样:

    scrapy crawl scrapyd_cn
    

    2、check:检查蜘蛛

    3、list:显示有多少个蜘蛛

    这里的蜘蛛就是指spider文件夹下面xx.py文件中定义的name,你有10个py文件但是只有一个定义了蜘蛛的name,那只算一个蜘蛛,比如我们在:AoiSolas目录下运行这个命令:

    scrapy list
    

     

    它其实就是得到了我们的蜘蛛名字!

    scrapy.Spider

    name:定义此蜘蛛名称的字符串。

    allowed_domains:包含允许此蜘蛛爬行的域的字符串的可选列表。

    start_urls:当没有指定特定的URL时,蜘蛛将从中开始爬行的URL列表。

    custom_settings:运行此spider时,将从项目范围配置中重写的设置字典。它必须被定义为类属性,因为在实例化之前更新了设置。-----用来覆盖全局配置

    crawler:此属性由 from_crawler() 初始化类后的类方法,并链接到 Crawler 此蜘蛛实例绑定到的对象。Crawler封装了项目中的许多组件,用于它们的单入口访问(例如扩展、中间件、信号管理器等)。

    settings:用于运行此蜘蛛的配置。这是一个 Settings 实例  

    logger:用蜘蛛创建的python记录器 name . 您可以使用它通过它发送日志消息

    from_crawler(crawler*args**kwargs):这是Scrapy用来创建蜘蛛的类方法。此方法设置了 crawler 和 settings 新实例中的属性,以便稍后在蜘蛛代码中访问它们。

    start_requests():此方法必须返回一个iterable,其中包含对此spider进行爬网的第一个请求。当蜘蛛被打开爬取的时候,它被称为 Scrapy。Scrapy只调用一次

    class MySpider(scrapy.Spider):
        name = 'myspider'
    
        def start_requests(self):
            return [scrapy.FormRequest("http://www.example.com/login",
                                       formdata={'user': 'john', 'pass': 'secret'},
                                       callback=self.logged_in)]
    
        def logged_in(self, response):
            # here you would extract links to follow and return Requests for
            # each of them, with another callback
            pass
    

    parse(response):这是Scrapy在请求未指定回调时用来处理下载响应的默认回调。这个 parse 方法负责处理响应,并返回 爬取 的数据和/或更多的URL。此方法以及任何其他请求回调都必须返回 Request 和/或DICT或 Item 物体。 

    log(message[, levelcomponent]):通过Spider的 logger ,保持向后兼容性。 

    closed(reason):蜘蛛关闭时调用。此方法为 spider_closed 信号。

    Item Pipeline-项目管道(数据清洗,去重,存储到数据库)

    项目管道的典型用途有:

    • 清理HTML数据
    • 验证抓取的数据(检查项目是否包含某些字段)
    • 检查重复项(并删除它们)
    • 将爬取的项目存储在数据库中

    编写自己的项目管道

    每个item pipeline组件都是一个python类,必须实现以下方法:

    process_item(selfitemspider)---通过yield生成的item,spider是运行的爬虫实例

    对每个项管道组件调用此方法。 process_item() 必须:返回包含数据的dict,返回 Item (或任何后代类)对象,返回 Twisted Deferred 或提高 DropItem 例外。删除的项不再由其他管道组件处理。

      成功返回:返回 Item (或任何后代类)对象,

      失败返回:DropItem 
        

    open_spider(selfspider):当spider打开时调用此方法

    close_spider(selfspider):当spider关闭时调用此方法。

    from_crawler(clscrawler):获取项目的配置信息

    例子:价格处理pipeLine

    from scrapy.exceptions import DropItem
    
    class PricePipeline(object):
    
        vat_factor = 1.15
    
        def process_item(self, item, spider):
            if item.get('price'):
                if item.get('price_excludes_vat'):
                    item['price'] = item['price'] * self.vat_factor
                return item
            else:
                raise DropItem("Missing price in %s" % item)
    

      

    例子:写入JSON

    import json
    
    class JsonWriterPipeline(object):
    
        def open_spider(self, spider):
            self.file = open('items.jl', 'w')
    
        def close_spider(self, spider):
            self.file.close()
    
        def process_item(self, item, spider):
            line = json.dumps(dict(item)) + "
    "
            self.file.write(line)
            return item
    

      

    例子:写入Mongo

    import pymongo
    
    class MongoPipeline(object):
    
        collection_name = 'scrapy_items'
    
        def __init__(self, mongo_uri, mongo_db):
            self.mongo_uri = mongo_uri
            self.mongo_db = mongo_db
    
        @classmethod
        def from_crawler(cls, crawler):
            return cls(
                mongo_uri=crawler.settings.get('MONGO_URI'),
                mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
            )
    
        def open_spider(self, spider):
            self.client = pymongo.MongoClient(self.mongo_uri)
            self.db = self.client[self.mongo_db]
    
        def close_spider(self, spider):
            self.client.close()
    
        def process_item(self, item, spider):
            self.db[self.collection_name].insert_one(dict(item))
            return item
    

      

    例子:下载图片

    import scrapy
    import hashlib
    from urllib.parse import quote
    
    
    class ScreenshotPipeline(object):
        """Pipeline that uses Splash to render screenshot of
        every Scrapy item."""
    
        SPLASH_URL = "http://localhost:8050/render.png?url={}"
    
        def process_item(self, item, spider):
            encoded_item_url = quote(item["url"])
            screenshot_url = self.SPLASH_URL.format(encoded_item_url)
            request = scrapy.Request(screenshot_url)
            dfd = spider.crawler.engine.download(request, spider)
            dfd.addBoth(self.return_item, item)
            return dfd
    
        def return_item(self, response, item):
            if response.status != 200:
                # Error happened, return item.
                return item
    
            # Save screenshot to file, filename will be hash of url.
            url = item["url"]
            url_hash = hashlib.md5(url.encode("utf8")).hexdigest()
            filename = "{}.png".format(url_hash)
            with open(filename, "wb") as f:
                f.write(response.body)
    
            # Store filename in item.
            item["screenshot_filename"] = filename
            return item
    

      

    例子:去重

    from scrapy.exceptions import DropItem
    
    class DuplicatesPipeline(object):
    
        def __init__(self):
            self.ids_seen = set()
    
        def process_item(self, item, spider):
            if item['id'] in self.ids_seen:
                raise DropItem("Duplicate item found: %s" % item)
            else:
                self.ids_seen.add(item['id'])
                return item
    

      

    DownLoader Middleware--下载器中间件

    可以在request进入下载器的时候和response进入爬虫的时候,,在这个过程中可以对request和response进行改写

    下载器中间件是Scrapy请求/响应处理的钩子框架。

    编写自己的下载中间件

    主要入口点是 from_crawler 类方法,它接收 Crawler 实例。这个 Crawler 例如,对象允许您访问 settings .

    classscrapy.downloadermiddlewares.DownloaderMiddleware

    process_request(requestspider):对于通过下载中间件的每个请求调用此方法。

    返回 None :对整个框架无影响

    返回 Response 对象:不在调用其他中间件的process_request方法,而会执行其他中间件的process_response

    返回 Request 对象:将request重新放入调度队列,一直重复

    返回 IgnoreRequest :异常,被其他中间件的process_exception() 进行捕获,如果它们都不处理异常,则请求的errback函数 (Request.errback ),如果没有代码处理引发的异常,则忽略该异常,不记录该异常(与其他异常不同)。

    请求的时候加代理,可以执行此方法

    process_response(requestresponsespider):

    返回 Response 对象:继续运行其他中间件的process_response方法,对其他中间件没有什么影响

    返回 Request 对象:不会在调用其他中间键的process_response方法,将request重新加入调度队列中进行下载

    返回 IgnoreRequest异常:调用异常处理方法,进行异常处理

    改写状态码

      

    process_exception(requestexceptionspider):捕捉异常,处理异常

    返回 None :不影响其他操作,Scrapy将继续处理异常,执行其他中间件的 process_exception() 方法,直到没有中间件,默认的异常处理开始。

    返回 Response 对象:执行其他中间件的 process_response() 方法,Scrapy不会调用其他process_exception方法。

    返回 Request 对象:重新加入调度队列以便将来下载。Scrapy不会调用其他process_exception方法。

    可以进行失败的重试

    捕捉异常后,可以添加代理再次重试

      

    超时时间的设定-在spider中

      

    from_crawler(clscrawler):获取项目的配置信息

      

      

    CookiesMiddleware

    此中间件允许使用需要cookie的站点,例如那些使用会话的站点。

    请思考 parse()方法的工作机制:

    1. 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
    2. 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息。
    3. scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
    4. 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
    5. parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
    6. Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路)
    7. 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
    8. 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items。
    7. 这一切的一切,Scrapy引擎和调度器将负责到底。

      

      

    CrawlSpiders

    创建带模板的spider

    scrapy genspider -t crawl tencent tencent.com
    

     

    spider

    import scrapy
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    from mySpider.items import TencentItem
    
    class TencentSpider(CrawlSpider):
        name = "tencent"
        allowed_domains = ["hr.tencent.com"]
        start_urls = [
            "http://hr.tencent.com/position.php?&start=0#a"
        ]
    
        page_lx = LinkExtractor(allow=("start=d+"))
    
        rules = [
            Rule(page_lx, callback = "parseContent", follow = True)
        ]
    
        def parseContent(self, response):
            for each in response.xpath('//*[@class="even"]'):
                name = each.xpath('./td[1]/a/text()').extract()[0]
                detailLink = each.xpath('./td[1]/a/@href').extract()[0]
                positionInfo = each.xpath('./td[2]/text()').extract()[0]
    
                peopleNumber = each.xpath('./td[3]/text()').extract()[0]
                workLocation = each.xpath('./td[4]/text()').extract()[0]
                publishTime = each.xpath('./td[5]/text()').extract()[0]
                #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
    
                item = TencentItem()
                item['name']=name.encode('utf-8')
                item['detailLink']=detailLink.encode('utf-8')
                item['positionInfo']=positionInfo.encode('utf-8')
                item['peopleNumber']=peopleNumber.encode('utf-8')
                item['workLocation']=workLocation.encode('utf-8')
                item['publishTime']=publishTime.encode('utf-8')
    
                yield item
    
        # parse() 方法不需要写     
        # def parse(self, response):                                              
        #     pass
    

      

    Logging

    Scrapy提供了log功能,可以通过 logging 模块使用。

    可以修改配置文件settings.py,任意位置添加下面两行,效果会清爽很多。

    LOG_FILE = "TencentSpider.log"
    LOG_LEVEL = "INFO"
    

    Log levels

    • Scrapy提供5层logging级别:

    • CRITICAL - 严重错误(critical)

    • ERROR - 一般错误(regular errors)
    • WARNING - 警告信息(warning messages)
    • INFO - 一般信息(informational messages)
    • DEBUG - 调试信息(debugging messages)

    logging设置

    通过在setting.py中进行以下设置可以被用来配置logging:

    1. LOG_ENABLED 默认: True,启用logging
    2. LOG_ENCODING 默认: 'utf-8',logging使用的编码
    3. LOG_FILE 默认: None,在当前目录里创建logging输出文件的文件名
    4. LOG_LEVEL 默认: 'DEBUG',log的最低级别
    5. LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示。

     

    发送POST请求

    class mySpider(scrapy.Spider):
        # start_urls = ["http://www.example.com/"]
    
        def start_requests(self):
            url = 'http://www.renren.com/PLogin.do'
    
            # FormRequest 是Scrapy发送POST请求的方法
            yield scrapy.FormRequest(
                url = url,
                formdata = {"email" : "mr_mao_hacker@163.com", "password" : "axxxxxxxe"},
                callback = self.parse_page
            )
        def parse_page(self, response):
            # do something
    

      

    模拟登陆

    通常网站通过 实现对某些表单字段(如数据或是登录界面中的认证令牌等)的预填充。

    使用Scrapy抓取网页时,如果想要预填充或重写像用户名、用户密码这些表单字段, 可以使用 FormRequest.from_response() 方法实现。

    下面是使用这种方法的爬虫例子:

    import scrapy
    
    class LoginSpider(scrapy.Spider):
        name = 'example.com'
        start_urls = ['http://www.example.com/users/login.php']
    
        def parse(self, response):
            return scrapy.FormRequest.from_response(
                response,
                formdata={'username': 'john', 'password': 'secret'},
                callback=self.after_login
            )
    
        def after_login(self, response):
            # check login succeed before going on
            if "authentication failed" in response.body:
                self.log("Login failed", level=log.ERROR)
                return
    
            # continue scraping with authenticated session...
    

      

    修改替换链接

     

    scrapy模拟登录的三种方式

    注意:模拟登陆时,必须保证settings.py里的 COOKIES_ENABLED(Cookies中间件) 处于开启状态

    COOKIES_ENABLED = True 或 # COOKIES_ENABLED = False
    

      

    策略一:直接POST数据(比如需要登陆的账户信息)

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class Renren1Spider(scrapy.Spider):
        name = "renren1"
        allowed_domains = ["renren.com"]
    
        def start_requests(self):
            url = 'http://www.renren.com/PLogin.do'
            # FormRequest 是Scrapy发送POST请求的方法
            yield scrapy.FormRequest(
                    url = url,
                    formdata = {"email" : "mr_mao_hacker@163.com", "password" : "axxxxxxxe"},
                    callback = self.parse_page)
    
        def parse_page(self, response):
            with open("mao2.html", "w") as filename:
                filename.write(response.body)
    

      

    策略二:标准的模拟登陆步骤

    正统模拟登录方法:
    
    首先发送登录页面的get请求,获取到页面里的登录必须的参数(比如说zhihu登陆界面的 _xsrf)
    
    然后和账户密码一起post到服务器,登录成功
    

      

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    
    class Renren2Spider(scrapy.Spider):
        name = "renren2"
        allowed_domains = ["renren.com"]
        start_urls = (
            "http://www.renren.com/PLogin.do",
        )
    
        # 处理start_urls里的登录url的响应内容,提取登陆需要的参数(如果需要的话)
        def parse(self, response):
            # 提取登陆需要的参数
            #_xsrf = response.xpath("//_xsrf").extract()[0]
    
            # 发送请求参数,并调用指定回调函数处理
            yield scrapy.FormRequest.from_response(
                    response,
                    formdata = {"email" : "mr_mao_hacker@163.com", "password" : "axxxxxxxe"},#, "_xsrf" = _xsrf},
                    callback = self.parse_page
                )
    
        # 获取登录成功状态,访问需要登录后才能访问的页面
        def parse_page(self, response):
            url = "http://www.renren.com/422167102/profile"
            yield scrapy.Request(url, callback = self.parse_newpage)
    
        # 处理响应内容
        def parse_newpage(self, response):
            with open("xiao.html", "w") as filename:
                filename.write(response.body)
    

      

    策略三:直接使用保存登陆状态的Cookie模拟登陆

    如果实在没办法了,可以用这种方法模拟登录,虽然麻烦一点,但是成功率100%

    # -*- coding: utf-8 -*-
    import scrapy
    
    class RenrenSpider(scrapy.Spider):
        name = "renren"
        allowed_domains = ["renren.com"]
        start_urls = (
            'http://www.renren.com/111111',
            'http://www.renren.com/222222',
            'http://www.renren.com/333333',
        )
    
        cookies = {
        "anonymid" : "ixrna3fysufnwv",
        "_r01_" : "1",
        "ap" : "327550029",
        "JSESSIONID" : "abciwg61A_RvtaRS3GjOv",
        "depovince" : "GW",
        "springskin" : "set",
        "jebe_key" : "f6fb270b-d06d-42e6-8b53-e67c3156aa7e%7Cc13c37f53bca9e1e7132d4b58ce00fa3%7C1484060607478%7C1%7C1486198628950",
        "t" : "691808127750a83d33704a565d8340ae9",
        "societyguester" : "691808127750a83d33704a565d8340ae9",
        "id" : "327550029",
        "xnsid" : "f42b25cf",
        "loginfrom" : "syshome"
        }
    
        # 可以重写Spider类的start_requests方法,附带Cookie值,发送POST请求
        def start_requests(self):
            for url in self.start_urls:
                yield scrapy.FormRequest(url, cookies = self.cookies, callback = self.parse_page)
    
        # 处理响应内容
        def parse_page(self, response):
            print "===========" + response.url
            with open("deng.html", "w") as filename:
                filename.write(response.body)
    

      

     

    知乎爬虫参考

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy.selector import Selector
    from scrapy.linkextractors import LinkExtractor
    from scrapy import Request, FormRequest
    from zhihu.items import ZhihuItem
    
    class ZhihuSipder(CrawlSpider) :
        name = "zhihu"
        allowed_domains = ["www.zhihu.com"]
        start_urls = [
            "http://www.zhihu.com"
        ]
        rules = (
            Rule(LinkExtractor(allow = ('/question/d+#.*?', )), callback = 'parse_page', follow = True),
            Rule(LinkExtractor(allow = ('/question/d+', )), callback = 'parse_page', follow = True),
        )
    
        headers = {
        "Accept": "*/*",
        "Accept-Encoding": "gzip,deflate",
        "Accept-Language": "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4",
        "Connection": "keep-alive",
        "Content-Type":" application/x-www-form-urlencoded; charset=UTF-8",
        "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": "http://www.zhihu.com/"
        }
    
        #重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数
        def start_requests(self):
            return [Request("https://www.zhihu.com/login", meta = {'cookiejar' : 1}, callback = self.post_login)]
    
        def post_login(self, response):
            print 'Preparing login'
            #下面这句话用于抓取请求网页后返回网页中的_xsrf字段的文字, 用于成功提交表单
            xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
            print xsrf
            #FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单
            #登陆成功后, 会调用after_login回调函数
            return [FormRequest.from_response(response,   #"http://www.zhihu.com/login",
                                meta = {'cookiejar' : response.meta['cookiejar']},
                                headers = self.headers,  #注意此处的headers
                                formdata = {
                                '_xsrf': xsrf,
                                'email': '1095511864@qq.com',
                                'password': '123456'
                                },
                                callback = self.after_login,
                                dont_filter = True
                                )]
    
        def after_login(self, response) :
            for url in self.start_urls :
                yield self.make_requests_from_url(url)
    
        def parse_page(self, response):
            problem = Selector(response)
            item = ZhihuItem()
            item['url'] = response.url
            item['name'] = problem.xpath('//span[@class="name"]/text()').extract()
            print item['name']
            item['title'] = problem.xpath('//h2[@class="zm-item-title zm-editable-content"]/text()').extract()
            item['description'] = problem.xpath('//div[@class="zm-editable-content"]/text()').extract()
            item['answer']= problem.xpath('//div[@class=" zm-editable-content clearfix"]/text()').extract()
            return item
    

      

    反爬虫,在下载中间键

    • 动态设置User-Agent(随机切换User-Agent,模拟不同用户的浏览器信息)

    • 禁用Cookies(也就是不启用cookies middleware,不向Server发送cookies,有些网站通过cookie的使用发现爬虫行为)

      • 可以通过COOKIES_ENABLED 控制 CookiesMiddleware 开启或关闭
    • 设置延迟下载(防止访问过于频繁,设置为 2秒 或更高)

    • Google Cache 和 Baidu Cache:如果可能的话,使用谷歌/百度等搜索引擎服务器页面缓存获取页面数据。

    • 使用IP地址池:VPN和代理IP,现在大部分网站都是根据IP来ban的。

    • 使用 Crawlera(专用于爬虫的代理组件),正确配置和设置下载中间件后,项目所有的request都是通过crawlera发出。

      •   
         DOWNLOADER_MIDDLEWARES = {
              'scrapy_crawlera.CrawleraMiddleware': 600
          }
        
          CRAWLERA_ENABLED = True
          CRAWLERA_USER = '注册/购买的UserKey'
          CRAWLERA_PASS = '注册/购买的Password'
        

          

    下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。

    process_request(self, request, spider)

    • 当每个request通过下载中间件时,该方法被调用。

    • process_request() 必须返回以下其中之一:一个 None 、一个 Response 对象、一个 Request 对象或 raise IgnoreRequest:

      • 如果其返回 None ,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。

      • 如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。

      • 如果其返回 Request 对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。

      • 如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。

    • 参数:

      • request (Request 对象) – 处理的request
      • spider (Spider 对象) – 该request对应的spider

    process_response(self, request, response, spider)

    当下载器完成http请求,传递响应给引擎的时候调用

    • process_request() 必须返回以下其中之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。

      • 如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。

      • 如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。

      • 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。

    • 参数:

      • request (Request 对象) – response所对应的request
      • response (Response 对象) – 被处理的response
      • spider (Spider 对象) – response所对应的spider
    1. 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);

    2. 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)

    要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。

    中间件案例

    # middlewares.py
    
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import random
    import base64
    
    from settings import USER_AGENTS
    from settings import PROXIES
    
    # 随机的User-Agent
    class RandomUserAgent(object):
        def process_request(self, request, spider):
            useragent = random.choice(USER_AGENTS)
    
            request.headers.setdefault("User-Agent", useragent)
    
    class RandomProxy(object):
        def process_request(self, request, spider):
            proxy = random.choice(PROXIES)
    
            if proxy['user_passwd'] is None:
                # 没有代理账户验证的代理使用方式
                request.meta['proxy'] = "http://" + proxy['ip_port']
            else:
                # 对账户密码进行base64编码转换
                base64_userpasswd = base64.b64encode(proxy['user_passwd'])
                # 对应到代理服务器的信令格式里
                request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd
                request.meta['proxy'] = "http://" + proxy['ip_port']
    

      

    反爬很厉害,可以利用百度快照爬到网页数据

    2. 修改settings.py配置USER_AGENTS和PROXIES

    • 添加USER_AGENTS:
      •  USER_AGENTS = [
            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
            "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
            "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
            "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
            "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
            ]
        
    • 添加代理IP设置PROXIES:
      • ROXIES = [
            {'ip_port': '111.8.60.9:8123', 'user_passwd': 'user1:pass1'},
            {'ip_port': '101.71.27.120:80', 'user_passwd': 'user2:pass2'},
            {'ip_port': '122.96.59.104:80', 'user_passwd': 'user3:pass3'},
            {'ip_port': '122.224.249.122:8088', 'user_passwd': 'user4:pass4'},
        ]
    • 除非特殊需要,禁用cookies,防止某些网站根据Cookie来封锁爬虫。
      • COOKIES_ENABLED = False
    • 设置下载延迟
      • DOWNLOAD_DELAY = 3
    • 最后设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类。
      • DOWNLOADER_MIDDLEWARES = {
            #'mySpider.middlewares.MyCustomDownloaderMiddleware': 543,
            'mySpider.middlewares.RandomUserAgent': 1,
            'mySpider.middlewares.ProxyMiddleware': 100
        }
    •     
        

      

    Settings

    Scrapy设置(settings)提供了定制Scrapy组件的方法。可以控制包括核心(core),插件(extension),pipeline及spider组件。比如 设置Json Pipeliine、LOG_LEVEL等。

    参考文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/settings.html#topics-settings-ref

    内置设置参考手册

    • BOT_NAME

      • 默认: 'scrapybot'

      • 当您使用 startproject 命令创建项目时其也被自动赋值。

    • CONCURRENT_ITEMS

      • 默认: 100

      • Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。

    • CONCURRENT_REQUESTS
      • 默认: 16

      • Scrapy downloader 并发请求(concurrent requests)的最大值。

    • DEFAULT_REQUEST_HEADERS
      • 默认: 如下

        {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en',
        }
        

        Scrapy HTTP Request使用的默认header。

    • DEPTH_LIMIT

      • 默认: 0

      • 爬取网站最大允许的深度(depth)值。如果为0,则没有限制。

    • DOWNLOAD_DELAY
      • 默认: 0

      • 下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:

      DOWNLOAD_DELAY = 0.25 # 250 ms of delay

      • 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。
    • DOWNLOAD_TIMEOUT

      • 默认: 180

      • 下载器超时时间(单位: 秒)。

    • ITEM_PIPELINES
      • 默认: {}

      • 保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意,不过值(value)习惯设置在0-1000范围内,值越小优先级越高。

        ITEM_PIPELINES = {
        'mySpider.pipelines.SomethingPipeline': 300,
        'mySpider.pipelines.ItcastJsonPipeline': 800,
        }
        
    • LOG_ENABLED

      • 默认: True

      • 是否启用logging。

    • LOG_ENCODING

      • 默认: 'utf-8'

      • logging使用的编码。

    • LOG_LEVEL

      • 默认: 'DEBUG'

      • log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。

    • USER_AGENT
      • 默认: "Scrapy/VERSION (+http://scrapy.org)"

      • 爬取的默认User-Agent,除非被覆盖。

    • PROXIES: 代理设置
      • 示例:

        PROXIES = [
          {'ip_port': '111.11.228.75:80', 'password': ''},
          {'ip_port': '120.198.243.22:80', 'password': ''},
          {'ip_port': '111.8.60.9:8123', 'password': ''},
          {'ip_port': '101.71.27.120:80', 'password': ''},
          {'ip_port': '122.96.59.104:80', 'password': ''},
          {'ip_port': '122.224.249.122:8088', 'password':''},
        ]
        
    • COOKIES_ENABLED = False
      • 禁用Cookies

      

    {'downloader/exception_count': 1781,         # 异常数量
    'downloader/exception_type_count/twisted.internet.error.ConnectionRefusedError': 34,            # 链接错误数量
    'downloader/exception_type_count/twisted.internet.error.TimeoutError': 1724,       # 超时数量
    'downloader/exception_type_count/twisted.web._newclient.ResponseFailed': 7,        # 页面响应错误数量
    'downloader/exception_type_count/twisted.web._newclient.ResponseNeverReceived': 16,    # 没有找到的数量
    'downloader/request_bytes': 11834762,     # 请求字节大小
    'downloader/request_count': 30080,        # 请求次数
    'downloader/request_method_count/GET': 30080,    # GET请求次数
    'downloader/response_bytes': 762108730,    # 响应字节大小
    'downloader/response_count': 28299,       # 响应次数
    'downloader/response_status_count/200': 28299,    # 状态码为200的次数
    'finish_reason': 'finished',    # 爬虫结束原因
    'finish_time': datetime.datetime(2019, 8, 5, 8, 22, 4, 856127),    # 爬虫结束时间
    'log_count/ERROR': 1,    # 日志记录ERROR等级次数
    'log_count/INFO': 52,    # 日志记录INFO等级次数
    'log_count/WARNING': 7,    # 日志记录WARNING等级次数
    'memusage/max': 315863040,    # 最大占用内存
    'memusage/startup': 58642432,    # 启动占用内存
    'request_depth_max': 1,    # 最大请求深度
    'response_received_count': 28299,    # 接收响应次数
    'retry/count': 1773,    # 重尝试次数
    'retry/max_reached': 8,    # 最大重尝试次数
    'retry/reason_count/twisted.internet.error.ConnectionRefusedError': 34,    # 重尝试链接报错次数
    'retry/reason_count/twisted.internet.error.TimeoutError': 1716,    # 重尝试超时报错次数
    'retry/reason_count/twisted.web._newclient.ResponseFailed': 7,    # 页面错误重尝试次数
    'retry/reason_count/twisted.web._newclient.ResponseNeverReceived': 16,    # 没有找到重尝试次数
    'scheduler/dequeued': 30080,    # 调度器移除
    'scheduler/dequeued/memory': 30080,
    'scheduler/enqueued': 30080,
    'scheduler/enqueued/memory': 30080,
    'start_time': datetime.datetime(2019, 8, 5, 7, 59, 16, 803793)}    # 爬虫开始时间
    2019-08-05 08:22:04 [scrapy.core.engine] INFO: Spider closed (finished)     # 爬虫结束
    

      

  • 相关阅读:
    Element Pagination分页组件 二次封装
    vue 发送短信验证码倒计时
    生成动态海报,带二维码
    H5九宫格抽奖,亲测可用
    elementui el-select使用远程搜索单选,输入内容不会触发remote-method
    日期格式转换,转换格式YYYY-MM-DD HH:mm:ss
    bignumber.js是一款用于任意精度十进制和非十进制算术的JavaScript库
    element table切换分页不勾选的自带方法
    密码强度校验
    webdriver-设置代理
  • 原文地址:https://www.cnblogs.com/shuimohei/p/13340258.html
Copyright © 2020-2023  润新知