本文主要记录scrapy安装、配置,到第一个爬虫实例实现的过程。
准备工作
环境
- 环境:macOS
- python版本:python 3.7
- Scrapy版本:1.5.1
安装scrapy
1 | pip3 install scrapy |
创建Scrapy项目
创建python目录,切换到python目录,创建爬虫项目
1 | magicyou@magicYoudeMacBook-Pro ~$ mkdir ~/python |
创建一个Spider爬虫程序。本文进行抓取的模板网站 ‘blog.jobbole.com’,
1 | magicyou@magicYoudeMacBook-Pro python$ cd articleSpider |
创建一个mysql表,用来存储文章信息
直接上sql
1 | DROP TABLE IF EXISTS `article`; |
至此准备工作基本完成
此时的目录结构应该是如下展示:
1 | |-- articleSpider |
简单的了解
Spiders
Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。
对spider来说,爬取的循环类似下文:
以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。
spider中初始的request是通过调用 start_requests() 来获取的。 start_requests() 读取 start_urls 中的URL, 并以 parse 为回调函数生成 Request 。
在回调函数内分析返回的(网页)内容,返回 Item 对象或者 Request 或者一个包括二者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。
在回调函数内,您可以使用 选择器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。
最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports
Items
爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item 类来满足这样的需求。
Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。
Item Pipeline
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
- 清理HTML数据
- 验证爬取的数据(检查item包含某些字段)
- 查重(并丢弃)
- 将爬取结果保存到数据库中
爬虫逻辑编写
入口文件 main.py
编辑器是PyCharm,为了调试方便,添加main.py作为程序的入口,切换在articleSpider操作(当前路径 ~/python/articleSpider/articleSpider)
1 | #!/usr/bin/python3 |
Spider爬虫使用parse(self,response)方法来解析所下载的页面。此方法返回一个包含新的URL资源网址的迭代对象,这些新的URL网址将被添加到下载队列中以供将来进行爬取数据和解析。
spider主要代码(python/articleSpider/articleSpider/jobbole.py):
1 | # -*- coding: utf-8 -*- |
过程简单描述
- parse函数从start_urls为起始页,获取列表里的文章url,和文章的封面图url;
- 讲步骤1中的获取的url交给parse_detail,进行详情处理,处理结果交付给item;
- 获取下一页的url,作为参数再次交给parse函数,直到没有下一页为止。
Item使用简单的class定义语法以及 Field 对象来声明
items主要代码(python/articleSpider/items.py):
1 | import scrapy |
pipeline中接收item传过来的数据,并且存入数据库
1 | from scrapy.pipelines.images import ImagesPipeline |
最重要的一步就是settings需要配置
关键的settings配置:
1 | import os |
注意
- spiders目录下添加utils目录,存放常用的自定义公共方法common.py
1
2
3
4
5
6
7
8
9
10
11
12#!/usr/bin/python3
# -*- coding: utf-8 -*-
__author__ = 'magicYou'
import hashlib
def get_md5(url):
if isinstance(url, str):
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url)
return m.hexdigest() - articleSpider目录下创建images,用来存储下载的图片
知识补充
xpath基本语法
| 表达式 | 说明 |
|---|---|
| /body | 选出当前选择器的根元素body |
| /body/div | 选取当前选择器文档的根元素body的所有div子元素 |
| /body/div[1] | 选取body根元素下面第一个div子元素 |
| /body/div[last()] | 选取body根元素下面最后一个div子元素 |
| /body/div[last()-1] | 选取body根元素下面倒数第二个div子元素 |
| //div | 选取所有div子元素(不论出现在文档任何地方) |
| body//div | 选取所有属于body元素的后代的div元素(不论出现在body下的任何地方) |
| /body/@id | 选取当前选择器文档的根元素body的id属性 |
| //@class | 选取所有元素的class属性 |
| //div[@class] | 选取所有拥有class属性的div元素 |
| //div[@class=’bold’] | 选取所有class属性等于bold的div元素 |
| //div[contains(@class,’bold’)] | 选取所有class属性包含bold的div元素 |
| /div/* | 选取当前文档根元素div的所有子元素 |
| //* | 选取文档所有节点 |
| //div[@*] | 获取所有带属性的div元素 |
| //div/a | //div/p | 选取所有div元素下面的子元素a和子元素p(并集) |
| //p[@id=’content’]/text() | 选取id为content的p标签的内容(子元素的标签和内容都不会获取到) |
一个用法实例:
1 | txt_header = response.xpath("//div[@id='wrapper_header']/h3/text()").extract()[0] # 选取wrapper_header下h3标签的内容 |
CSS选择器
| 表达式 | 说明 |
|---|---|
| * | 选择所有节点 |
| #container | 选择Id为container的节点 |
| .container | 选取所有包含container类的节点 |
| li a | 选取所有li下的所有后代a元素(子和孙等所有的都会选中) |
| ul + p | 选取ul后面的第一个相邻兄弟p元素 |
| div#container > ul | 选取id为container的div的所有ul子元素 |
| ul ~ p | 选取与ul元素后面的所有兄弟p元素 |
| a[title] | 选取所有有title属性的a元素 |
| a[href=’http://taobao.com'] | 选取所有href属性等于http://taobao.com的a元素 |
| a[href*=’taobao’] | 选取所有href属性包含taobao的a元素 |
| a[href^=’http’] | 选取所有href属性开头为http的a元素 |
| a[href$=’.com’] | 选取所有href属性结尾为.com的a元素 |
| input[type=radio]:checked | 选取选中的radio的input元素 |
| div:not(#container) | 选取所有id非container的div元素 |
| li:nth-child(3) | 选取第三个li元素 |
| tr:nth-child(2n) | 选取偶数位的tr元素 |
| a::attr(href) | 获取所有a元素的href属性值 |
一个用法实例:
1 | txt_header = response.css("#wrapper_header h3::text").extract_first() # 选取wrapper_header下h3标签的内容 |