Search This Blog

Scrapy学习(一)

动机

近期参加阿里天池大数据竞赛 -- 大航杯“智造扬中”电力AI大赛,赛题提示中提及除已公布的用电量数据以外,可以额外使用例如天气、经济等数据。因此考虑通过爬虫的方式从天气网站上抓取历史天气数据。考虑到整体项目采用Python实现,因此爬虫也同样采用Python,而其中最为常用的即为Scrapy框架。

安装

依赖项

安装流程

1. 下载安装 VCForPython27.msi
2. 通过pip安装
pip2 install scrapy
在cmd窗口中输入以上命令,基于Python 2.x环境获取安装Scrapy框架。

第一个Scrapy程序

创建项目

在cmd窗口中定位到需要创建项目的目录,然后输入以下命令
code>scrapy startproject tutorial
将在当前目录下创建一个名为Tutorial的目录,具有如下的结构
tutorial/
    scrapy.cfg            # deploy configuration file

    tutorial/             # project's Python module, you'll import your code from here
        __init__.py

        items.py          # project items definition file

        pipelines.py      # project pipelines file

        settings.py       # project settings file

        spiders/          # a directory where you'll later put your spiders
            __init__.py

第一个Spider

注意到在tutorial目录下有一个spiders文件夹,spiders文件夹用于存放spiders,而每一个spider就是一个用户自定义的供Scrapy调用的类,这些spiders用于从关注的网址(或网址列表)中爬取相应感兴趣的数据。这些用户自定义的类必须 1) 继承自scrapy.Spider 2) 定义初始化的请求(链接)以及可选地 1) 定义如何跟踪页面上的链接 2) 如何提取所下载页面的内容。以下给出第一个spider的代码,将其保存为quotes_spider.py并存放在tutorial/spiders目录下。
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)
注意:在类定义的首行注明了name = "quotes",作为项目中spider的唯一标识,用于后续的调用。我们来逐一对照前述的两个必要与两个可选项。首先,所定义的Spider名称为QuotesSpider并且继承自scrapy.Spider;其次,定义了start_requests方法,在其中生成了初始的Request;注意到,该Request方法请求了以此请求urls中的两个网址,并且将爬取的页面交由parse方法进行处理。对于可选项1)提到的定义跟踪其他链接的方法在这个例子中并未给出。最后,关注parse方法,实际的功能是将爬取的页面保存到本地,暂时为进行页面的解析,后续将会给出页面解析的示例。

运行spider

在了解该spider的作用以后,我们来测试运行一下,运行的方法为,在cmd窗口中输入
scrapy crawl quotes
以上命令会在当前项目中搜素名为quotes的spider,找到后调用,开始爬取并保存网页。得到的结果与下面类似
D:\Tutorial\Python\Scrapy\tutorial>scrapy crawl quotes
2017-06-06 17:42:17 [scrapy.utils.log] INFO: Scrapy 1.4.0 started (bot: tutorial)
2017-06-06 17:42:17 [scrapy.utils.log] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'tutorial.spiders', 'SPIDER_MODULES': 
2017-06-06 17:42:18 [scrapy.core.engine] INFO: Spider opened
2017-06-06 17:42:19 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2017-06-06 17:42:19 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2017-06-06 17:42:20 [scrapy.core.engine] DEBUG: Crawled (404) <get http:="" quotes.toscrape.com="" robots.txt=""> (referer: None)
2017-06-06 17:42:21 [scrapy.core.engine] DEBUG: Crawled (200) <get http:="" quotes.toscrape.com=""> (referer: None)
...

提取数据

以上的例子中仅仅是保存了url列表中所有url对应的网页内容,这对于爬取而言并不足够,Scrapy还提供了强大的提取功能,可以不必直接整个将网页下载而是提取部分所关注的内容。总体而言,Scrapy提供了两种提取的方式
  • CSS
  • Xpath
为了了解如何通过这两种方式进行内容的提取,我们最好从Scrapy的Shell模式入手。可以把Shell模式作为一个调试的手段,逐条语句的测试。Ok,下面开始动手吧
在cmd窗口中输入以下语句
scrapy shell "http://quotes.toscrape.com/page/1/"
将会看到类似如下的结果
C:\Windows\system32>scrapy shell "http://quotes.toscrape.com/page/1/"
2017-06-08 16:07:24 [scrapy.utils.log] INFO: Scrapy 1.4.0 started (bot: scrapybot)
2017-06-08 16:07:24 [scrapy.utils.log] INFO: Overridden settings: {'LOGSTATS_INTERVAL': 0, 'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter'}
2017-06-08 16:07:25 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2017-06-08 16:07:25 [scrapy.core.engine] INFO: Spider opened
2017-06-08 16:07:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x044E5EF0>
[s]   item       {}
[s]   request    <GET http://quotes.toscrape.com/page/1/>
[s]   response   <200 http://quotes.toscrape.com/page/1/>
[s]   settings   <scrapy.settings.Settings object at 0x044E5FF0>
[s]   spider     <DefaultSpider 'default' at 0x47b1c10>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>>
此时已经将网页内容爬取,接下来可以尝试按照指定规则提取内容,例如,获取网页的标题
>>> response.css('title')
[<Selector xpath=u'descendant-or-self::title' data=u'<title>Quotes to Scrape</title>'>]
通过调用responsecss方法,其中的参数为'title',可以获得类Python中list的数据结构,返回的是网页中所有类型为title的对象。为了获取以上对象中的文本而不是对象本身的方法如下:
>>> response.css('title::text').extract()
[u'Quotes to Scrape']
此处和上一行命令的区别在于两处,第一css方法中的参数多了::text,并且多了一个extract()方法。首先通过增加::text告诉Scrapy仅仅提取title对象的文本对象;此外通过调用extract()方法将文本对象中的内容提取出来。需要注意的是这个方法仍然是作用于整个列表的(此处因为只有一个元素),如果只想提取一个首个元素,那么可以通过以下的方法实现。
>>> response.css('title::text').extract_first()
u'Quotes to Scrape'
除了这种方法以外,还可以通过Xpath的方式进行内容提取,如下
>>> response.xpath("///title")
[<Selector xpath='///title' data=u'<title>Quotes to Scrape</title>'>]
>>> response.xpath("///title/text()")
[<Selector xpath='///title/text()' data=u'Quotes to Scrape'>]
>>> response.xpath("///title/text()").extract()
[u'Quotes to Scrape']
关于Xpath的用法在以后需要更细致的学习。

产生下一个Request

截止目前,我们可以通过爬取url列表中的网页,并提取所需内容,已经迈出了关键一步,但是如果我们想爬取很多的网页,把所有的网址列表全部添加到url列表的方法显然是不实际的,所以就引出了下一个问题,如何在运行过程中产生新的Request,从而获取其他的网页,这样就可以让爬虫真正的“爬”起来。看下面的例子
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)
以上代码的关键在于以下语句
next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)
注意以上代码段是在parse方法中的。首先,通过css的提取方式获取了当前页面中指向下一页的url地址;判断这个对象是否存在,如果存在的话就调用response.urljoin方法创建一个url地址,然后通过scrapy.Request方法再次生成对“下一页”这个链接的请求,与此同时将获取的内容传递给回调函数parse,如此便形成了一个迭代的调用,可以让爬虫不停的爬取下去,直到没有下一页为止。

保存爬取的结果

通过以上的示例,我们现在可以从一个网页出发,迭代式地爬取相同性质的网页中所需要的内容,最后一个问题就是如何对爬取的结果进行保存呢?Scrapy同样提供了简单的方式
scrapy crawl quotes -o quotes.json
通过在cmd窗口中调用该方法即可将爬取的结果保存到quotes.json文件中。
需要注意的是,由于历史遗留原因Scrapy在写入json文件时是追加方式写入而不是覆盖方法,所以如果多次调用该命令会破坏json的格式,因此在多次调用前记得删除原文件或者更改保存的文件名。 至此,第一个爬虫程序就较为完整的呈现出来了。 

扬中市历史天气数据的抓取

仿照以上的例子,通过对扬中市历史天气网站的结构的查看,就可以实现以下的爬虫程序。
import scrapy
class QuotesSpider(scrapy.Spider):
    name = "yangzhongWeather"
 
    def start_requests(self):
        url = "http://lishi.tianqi.com/yangzhong/201501.html"
        yield scrapy.Request(url, self.parse)
 
    def parse(self, response):
        weathers = iter(response.css('div.tqtongji2 ul'))
        next(weathers)
        for weather in weathers:
            yield {
                "date": weather.css('li a::text').extract_first(),
                "high_degree": weather.css('li::text').extract()[0],
                "low_degree": weather.css('li::text').extract()[1],
                "weather": weather.css('li::text').extract()[2],
            }
  
         next_page = response.css('span.tqxiangqing a::attr(href)').extract()[-1]
         datemonth = str(next_page.split("/")[-1]) 
         if datemonth != "201609.html":
             yield scrapy.Request(next_page, callback=self.parse)

参考资料

No comments:

Post a Comment