使用scrapy爬取stl容器的时间复杂度
2017-12-08 16:24
507 查看
本篇博客是一个简单的scrapy应用教程,我们要用scrapy爬取 C++官网上(www.cplusplus.com/reference/stl)关于各个stl容器方法的时间复杂度文本内容,主要是find insert erase三个方法。
首先明确一下scrapy这个爬虫框架的大概流程,主要组件有调度器scheduler,下载器downloader,蜘蛛spider,实体管道item pipeline和scrapy引擎等。整个流程简化一下包括以下步骤
1. 调度器存储一个要爬取网址的优先队列,它来决定下一次要爬取哪个网址
2. 下载器下载对应的网页,得到一个response对象
3. spider从这个response对象提取出有用信息,构建成事先定义好的item对象,交由pipeline进一步处理
4. 通常爬取不会一次结束,所以spider可能会生成request对象,即发出新的请求给调度器
5. 以上步骤会一直重复直到调度器没有网址调度或是异常退出为止。
项目的工程目录如下所示:
main.py是应用入口,运行命令”scrapy crawl reference”,即启动名为reference的spider
reference.py是项目的主要代码,spider对response进行解析提取出数据以及发送新的request都是在这里进行。
items.py定义了最后生成数据的结构。
pipeline.py是对生成的item对象的处理。
似乎很简单,获得id为“complexity”section的文本内容即可。同时我们要注意到有些文本被
接下来分析如何进入这个页面,观察url,后缀都是stl_name/function_name 的形式,比如进入set分类set容器的find方法页面,那么url是 www.cplusplus.com/reference/stl/set/set/find 。所以关键是爬取容器名称。
由于只有关联容器才有find insert erase方法,分析一下上图的网页,可以看到,我们要爬取的是Associative contaniners文本的兄弟节点中,class为links的dl标签下的超链接文本内容(先喘口气)。将爬取的字符串与url前缀拼接,再加上find insert erase路径,我们就可以进入之前分析的那个页面了。
使用
是选择当前节点的子节点而不包括子节点的后代节点,
这篇博客列出了
当然,我们应清楚,xpath()提供的规则毕竟有限,有些情况下xpath()爬取特定的信息会很麻烦,这时候就需要用到更加强大更加灵活的正则表达式re()。由于本项目用不到正则表达式,所以暂时不做说明。
pipeline,顾名思义,就是对item进行流水线的处理,这里我要将最后生成的item存储到云服务器的redis数据库中,在pipeline.py中可以这么写:
https://github.com/Yuanpei-Wu/ContainerTimeComplexitySpider
首先明确一下scrapy这个爬虫框架的大概流程,主要组件有调度器scheduler,下载器downloader,蜘蛛spider,实体管道item pipeline和scrapy引擎等。整个流程简化一下包括以下步骤
1. 调度器存储一个要爬取网址的优先队列,它来决定下一次要爬取哪个网址
2. 下载器下载对应的网页,得到一个response对象
3. spider从这个response对象提取出有用信息,构建成事先定义好的item对象,交由pipeline进一步处理
4. 通常爬取不会一次结束,所以spider可能会生成request对象,即发出新的请求给调度器
5. 以上步骤会一直重复直到调度器没有网址调度或是异常退出为止。
1.创建工程
scrapy startproject TimeComplexity cd TimeComplexity scrapy genspider -t basic reference www.cplusplus.com
startproject创建工程,
genspider创建爬虫,参数
-t basic意思是使用basic爬虫模板创建爬虫,
reference是爬虫名
www.cplusplus.com是限定爬取的域名。
项目的工程目录如下所示:
main.py是应用入口,运行命令”scrapy crawl reference”,即启动名为reference的spider
reference.py是项目的主要代码,spider对response进行解析提取出数据以及发送新的request都是在这里进行。
items.py定义了最后生成数据的结构。
pipeline.py是对生成的item对象的处理。
2.分析
我们要得到stl容器基本方法的时间复杂度,从页面上分析一下如何获取。似乎很简单,获得id为“complexity”section的文本内容即可。同时我们要注意到有些文本被
<a>包裹,比如“size”,所以要提取
<section>内的文本以及其中的
<a>内的文本。
接下来分析如何进入这个页面,观察url,后缀都是stl_name/function_name 的形式,比如进入set分类set容器的find方法页面,那么url是 www.cplusplus.com/reference/stl/set/set/find 。所以关键是爬取容器名称。
由于只有关联容器才有find insert erase方法,分析一下上图的网页,可以看到,我们要爬取的是Associative contaniners文本的兄弟节点中,class为links的dl标签下的超链接文本内容(先喘口气)。将爬取的字符串与url前缀拼接,再加上find insert erase路径,我们就可以进入之前分析的那个页面了。
3.善用xpath()
接下来就是编写代码了。reference.py已经定义好了start_url和
parse()方法。
start_url就是我们初始爬取的网站,下载器会自动下载网页内容,生成response对象给
parse()方法处理。我们要做到的主要是对response对象使用
xpath()方法提取出有用信息。
start_urls = ['http://www.cplusplus.com/reference/stl/'] def parse(self, response): next_urls = response.xpath('//div[@id="I_content"]//b[contains(text(),"Associative")]//following-sibling::dl[@class="links"]//a/@href').extract() for next_url in next_urls: container_name=next_url.split('/')[2] find_url= "http://www.cplusplus.com"+next_url+"find/" yield Request(find_url,callback=self.parse_middle,meta={'path_type':'find_time','container_name':container_name}) insert_url="http://www.cplusplus.com"+next_url+"insert/" yield Request(insert_url,callback=self.parse_middle,meta={'path_type':'insert_time', aefa 'container_name':container_name}) erase_url = "http://www.cplusplus.com" + next_url + "erase/" yield Request(erase_url, callback=self.parse_middle,meta={'path_type':'erase_time','container_name':container_name})
使用
xpath()需要注意
/和
//的差别,我一开始就是误用了
/而爬取不出任何数据。
/
是选择当前节点的子节点而不包括子节点的后代节点,
//则包括,所以
/写在开头就是从根节点开始寻找,根节点没有找到后面写啥都没用了。
这篇博客列出了
xpath()的常用方法,https://www.cnblogs.com/MUMO/p/5732836.html 可以作为参考。这里我们用到了contains,follow-sibling等规则。只要善用
xpath()的规则,爬取一些简单的页面元素并不困难。
当然,我们应清楚,xpath()提供的规则毕竟有限,有些情况下xpath()爬取特定的信息会很麻烦,这时候就需要用到更加强大更加灵活的正则表达式re()。由于本项目用不到正则表达式,所以暂时不做说明。
4.在requset和response之间传递参数
parse()在爬取到容器名称后,生成新的url去爬取,并调用方法
parse_middle()进一步处理新爬取的网页。这就产生了一个问题,我们的find insert erase三个路径都是用
parse_middle()去处理的,那么
parse_middle()怎么知道路径参数呢?一个很简单的做法就是对
response.url处理,获得路径名称。另一种做法就是定义request对象的meta属性,把要传递的参数放进meta里。
parse_middle()从response对象获得meta就获得了要传递的参数了。
def parse_middle(self,response): result=response.xpath('//section[@id="complexity"]/text() | //section[@id="complexity"]/a/text()').extract() path_type=response.meta['path_type'] container_name=response.meta['container_name'] final_result="" for str in result: if str!='\n': final_result+=str item=ContainerItem() item['container_name']=container_name item[path_type]=final_result return item
5.item 与pipeline
在parse_middle()最后我们就爬出的信息保存为item对象。item就是预先定义好的爬取信息结构,在items.py中我们可以这么写:class ContainerItem(scrapy.Item): container_name=Field() find_time = Field() insert_time = Field() erase_time = Field()
pipeline,顾名思义,就是对item进行流水线的处理,这里我要将最后生成的item存储到云服务器的redis数据库中,在pipeline.py中可以这么写:
class TimecomplexityPipeline(object): def __init__(self): self.client=redis.Redis(host='****',port=6379,password='****') def process_item(self, item, spider): if spider.name=='reference': if dict(item).has_key('find_time') : self.client.hset('container_time_complexity::'+item['container_name'],'find_time',item['find_time']) if dict(item).has_key('insert_time'): self.client.hset('container_time_complexity::' + item['container_name'], 'insert_time', item['insert_time']) if dict(item).has_key('erase_time'): self.client.hset('container_time_complexity::' + item['container_name'], 'erase_time', item['erase_time']) return item
6.源码
完整的源码放在了github上:https://github.com/Yuanpei-Wu/ContainerTimeComplexitySpider
相关文章推荐
- STL中一些容器的使用
- STL中容器使用erase()遍历删除
- STL中Vector容器Find的使用(泛型)
- STL 容器使用方法
- STL容器Set和Multisets使用
- STL的基本使用之关联容器:map和multiMap的基本使用
- C++ STL 容器之栈的使用
- 算法导论12.2-8 从任意结点使用后继函数k次的时间复杂度为O(k+h)
- STL各种容器的使用时机详解
- STL各种容器的使用时机详解
- STL容器使用DEMO-set
- 使用“哨兵”减小时间复杂度
- 使用模板省略号参数模仿 STL 容器的 emplaceback
- 在使用STL容器时避免使用具有复杂拷贝构造函数的类
- STL容器使用案例文档
- 关于stl标准容器中的迭代器的使用注意事项
- 使用lua访问STL容器
- STL - 各个容器的使用时机
- STL容器erase的使用陷阱(三)
- 常见的STL容器及其使用场景