动机
近期参加阿里天池大数据竞赛 -- 大航杯“智造扬中”电力AI大赛,赛题提示中提及除已公布的用电量数据以外,可以额外使用例如天气、经济等数据。因此考虑通过爬虫的方式从天气网站上抓取历史天气数据。考虑到整体项目采用Python实现,因此爬虫也同样采用Python,而其中最为常用的即为Scrapy框架。
安装
依赖项
- Python 2.x
- VCForPython27.msi (for Python 2.x)
安装流程
1. 下载安装 VCForPython27.msi
2. 通过pip安装
pip2 install scrapy
在cmd窗口中输入以上命令,基于Python 2.x环境获取安装Scrapy框架。第一个Scrapy程序
创建项目
在cmd窗口中定位到需要创建项目的目录,然后输入以下命令
code>scrapy startproject 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>'>]
response
的css
方法,其中的参数为'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