爬虫项目过程:
- 创建一个scrapy项目
- 定义提取结构化数据item
- 编写 爬取网站的spider,并提出结构化数据item
- 编写 item piplines,来存储提取到的item,即结构化数据
一、创建一个简单的爬虫项目
1.创建scrapy项目:
在命令行下,
scrapy startproject mySpider
cd mySpider
2.目录结构,类似djano:
scrapy.cfg:项目的配置文件
myspider/:项目的python模块,将会从这里引用代码
mySpdier/items.py:项目的目标文件
mySpider/pipelines.py:项目的管道文件
mySpider/settings.py:项目的设置文件
mySpider/spiders/:存储爬虫代码的目录
2.创建爬虫文件:
cd mycast
scrapy genspider 爬虫文件的名字 "爬虫的域,如htpp://www.baidu.com"
3.运行爬虫:
测试爬虫是否正常:
scrapy check 爬虫名称
运行爬虫:
scrapy crawl 爬虫名称
二、明确爬取目标
如,我们爬取http://www.itcast.cn/channel/teacher.shtml网站的所有讲师的姓名、职称和个人信息。
1.编辑mySpider目录下的items.py文件
2.item定义结构化数据字段,用来保存爬取到的数据,类似字典,但是 提供了一些额外的保护,以减少错误
3.通过创建一个继承自scrapy.Item的类,并且定义类型为scrapy.Field的类属性来定义一个item;类似于djanog中的model.py的ORM映射。
示例:MyspiderItem,和构造item模型model
class MyspiderItem(scrapy.Item):
name = scrapy.Field()
level = scrapy.Field()
info = scrapy.Field()
三、制作爬虫
制作爬虫功能分两步:
1.爬数据:
cd mySpider:进入项目目录
创建xxx爬虫文件和xxxSpider.py爬虫类:
在当前目录下输入以下命令,将在mySpider/spider目录下创建一个名为itcast.py的爬虫,并指定爬取域的范围
scrapy genspider itcast "itcast.cn"
编写itcast.py文件,默认增加了下列代码。由以上命令生成的爬虫文件:
import scrapy
class ItcastSpider(scrapy.Spider):
# 爬虫名称,启动爬虫时必需的参数
name = "itcast"
# 爬虫域范围,允许爬虫在这个域名下进行爬取,可选
allowed_domains = ["htpp://www.itcast.cn"]
# 爬虫列表:要爬取的url地址列表
start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
def parse(self, response): pass
当然, 以上爬虫文件,也可以自己创建,不由scrapy genspider生成。
要创建一个爬虫文件时,必须要有三个属性和一个方法,不论是由scrapy genspider生成的爬虫文件还是自定义生成的爬虫文件:
name:必选,爬虫的识别名称,必须是唯一的
allow_domains:可选,搜索的范围,即爬虫的约束区域;规定爬虫只爬取这个域名下的网页,不在此范围内的url会被忽略
start_urls:必选,爬取的url列表。当没有指定url时,爬虫从这里开始抓取数据,所以,第一次下载的数据将从这些urls开始。其它的子url将会从这些起始url中继承性生成。
parse(self, response):解析的方法,必选;每个初始url完成下载后将调用此方法,调用的时侯传入从每个url返回的response对象来作为唯一参数。主要作用如下:
- 负责解析返回的网页数据response.body,提取结构化数据(生成item)
- 生成需要下一页的url请求
修改start_urls的值为需要爬取的第一个url
start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
修改parse()方法:
def parse(self, response):
with open('teacher.html', 'w') as f:
f.write(response.text)
2.取数据
观察页面源码,使用response.xpath提取数据。编写itcast.py的parse方法
筛选数据:这步是最重要的,需要在页面源码中观察
姓名 , //div[@class='li_txt']/h3
职称 , //div[@class='li_txt']/h4
个人介绍, //div[@class='li_txt']/p
import scrapy
from mySpider.items import ItcastItem
class ItcastSpider(scrapy.Spider):
name = "itcast"
allowed_domains = ["itcast.cn"]
start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
def parse(self, response):
# 存放老师信息的列表
items = []
for each in response.xpath("//div[@class='li_txt']"):
# item = ItcastItem()
item = {}
#extract()方法,返回的都是unicode字符串
name = each.xpath("h3/text()").extract()
title = each.xpath("h4/text()").extract()
info = each.xpath("p/text()").extract()
# xpath返回的是一个元素的列表
item['name'] = name[0]
item['title'] = title[0]
item['info'] = info[0]
items.append(item)
# 返回数据引擎,引擎判断判断不是item类型,将不会调用pipeline
return items
注意:以上parse()方法,返回的items是列表,不是item类型,因此引擎不会调用pipeline管道。
当不使用管道时,scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件
# json格式,默认为unicode编码
scrapy crawl itcast -o teachers.json
# json lines格式,默认为unicode编码
scrapy crawl itcast -o teachers.jsonl
# csv文件,逗号隔开,可用excel打开
scrapy crawl itcast -o teachers.csv
# xml格式
scrapy crawl itcast -o teachers.xml
查看爬虫名称列表:
scrapy list
执行爬虫:
scrapy craw itcast
注意:scrapy craw 后面是爬虫的名字
一个scrapy项目,可以包括多个爬虫。各个爬虫在执行时,按name属性区分。
如果运行后,打印日志出现[scrapy] INFO: Spider closed (finished),表示执行完成。
# 注意,python2默认的编码环境是ascii,当取回的数据不是ascii时,可能 会造成乱码
# 我们可以指定保存内容的编码格式,一般情况下,在代码最上方添加
import sys
reload(sys)
sys.setdefaultencodeing("utf-8")
python3编码默认是unicode,无需处理。
四、scrape.Spider源码:
import logging
class Spider(object_ref):
"""所有爬虫的基类,用户定义的爬虫必须继承自之个类"""
# name是spider最重要的属性,而且是必须的,唯一的;一般使用要爬取的网站名称来命令,可加可不加后缀
# name属性,定义了scrapy如何定位并初始化spider
name = None
custom_settings = None
# 初始化,提取爬虫名字和start_urls
def __init__(self, name=None, **kwargs):
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
# python对象或类型,通过内置成员__dict__来存储成员信息
self.__dict__.update(kwargs)
# 要爬取的url地址列表。当没有指定的url时,spider将从该列表中开始进行爬取。
# 因此,第一个被获取到的页面的url将是该列表中第一个url
if not hasattr(self, 'start_urls'):
self.start_urls = []
# 该方法将读取start_urls内地址,并为每个地址生成一个Request对象
# 交给scrapy下载并返回Response;该方法仅调用一次
def start_requests(self):
for url in self.start_urls:
yield self.make_requests_from_url(url)
# 实际生成Request对象;Request对象默认的回调函数为parse(),提交方式为get
def make_requests_from_url(self, url):
# dont_filter为True,不做去重处理;
# 每个请求,会生成一个特征码,存储在内存;
# 如果有相同的请求时,特征码将相同,就会根据此参数来判断是否要重复发送请求
return Request(url, dont_filter=True)
# 默认的Request对象回调函数,处理返回的response
# 生成item或Request对象。用户必须实现这个类。
def parse(self, response):
raise NotImplementedError
@property
def logger(self):
logger = logging.getLogger(self.name)
return logging.LoggerAdapter(logger, {'spider': self})
def log(self, message, level=logging.DEBUG, **kw):
self.logger.log(level, message, **kw)
...............
四、编写管道文件
用来处理item字段,写入数据库或文件中。
mySpider/pipelines.py
import json
# 此类必须实现process_item(self, item, spider)方法
class ItcastPipeline(object):
def __init__(self):
self.f = open("itcast_pipeline.json", "w")
def process_item(self, item, spider):
# 处理每个item,来自ItcastSpider.parse()返回的 yield item
# ensure_ascii为False,将处理中文
content = json.dumps(dict(item), ensure_ascii=False)
self.f.write(content.encode("utf-8"))
# 这里必须返回item,告诉引擎,我已经处理完了这个item
return item
def close_spider(self, spider):
self.f.close()
同时必须在mySpider/settings.py中,启用ITEM_PIPELINES ,将以上管道类,加入其中
mySpider目录.pipelines文件.ItcastPipeline类:权限比重
ITEM_PIPELINES = {
'mySpider.pipelines.ItcastPipeline': 300,
}
要调用管道文件,还必须mySpider/itcast.py文件中的Itcastspider类的parse()方法返回的数据是item的类型
def parse(self, response):
# 存放老师信息的列表
for each in response.xpath("//div[@class='li_txt']"):
# 创建item字段对象,用来存储信息
item = ItcastItem()
#xpath是对象;extract()方法,将xpath对象的data内容,转换为unicode字符串列表
name = each.xpath(".h3/text()").extract()
title = each.xpath(".h4/text()").extract()
info = each.xpath(".p/text()").extract()
# xpath返回的是一个元素的列表
item['name'] = name[0]
item['title'] = title[0]
item['info'] = info[0]
# items.append(item)
#yield返回给引擎;如果yield的数据是item类型,则会引擎会将之返回给管道;因为yield,还会回来继承取for循环的数据
yield item
当item在spider中被收集之后,它将会被传递到item pipeline,这些item pipeline组件按定义的顺序处理item。
每个item pipeline都是实现了简单方法的python类,比如决定此item是丢弃还是存储。
以下是item pipeline的一些典型应用:
- 验证爬取的数据(检查item包含某些字段,比如说name字段)
- 查重(并丢弃)
- 将爬取结果保存到文件或者数据库中
import something
class somethingPipelin(object):
def __init__(self):
"""
可选,做参数初始化等
"""
def process_item(self, item, spider):
"""
这个方法必须实现,每个item pipeline组件都需要调用该方法
这个方法必须返回一个item对象,被丢弃的item将不会被之后的pipeline组件所处理
:param item: Item对象,被爬取的item
:param spider: Spider对象,爬取该item的spider
:return: item,不返回的item将被丢弃
"""
def open_spider(self, spider):
"""
可选方法,当spider被开启时,这个方法被调用
:param spider: Spider对象,爬取该item的spider
:return:
"""
def close_spider(self, spider):
"""
可选方法,当spider被关闭时,这个方法被调用
:param spider: Spider对象,爬取该item的spider
:return:
"""
五、scrapy shell,用于测试
用于检查一个网站的响应,返回response
在项目根目录下执行以下命令:
scrapy shell 网站
response.body,包体
response.headers,包头
response.selector,获取response初始化类的Selector对象,此时可用response.selector.xpath()或response.selector.css()来对response进行查询
新版的scrapy,直接 response.xpath()或者response.css()
六、Selectors选择器:
scrapy selectors内置了xpath和css selector表达式机制
Selector有四个基本的方法,最常用的还是xpath:
- xpath():传入xpath表达式,返回该表达式所对应的所有节点的selector list列表,语法同XPATH。需要extract()序列化。
- extract():序列化该节点为unicode字符串并返回list
- css():传入css表达式,返回该表达式所对应的所有节点的selector列表,语法同BS4。需要extract()序列化。
- re():根据传入的正则表达 式对数据进行提取,返回unicode字符串列表。不需要extract()转换
extract_first(""):title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first("")
七、xpath
XPath表达式的例子及对应的含义
/html/head/title:选择<html>文件中<head>标签内的<title>元素
/html/head/title/text():选择上面的<title>元素的文字
//td:选择所有的<td>元素
//div[@class='mine']:选择所有具有class='mine'属性的div元素
//定位根节点
/往下层查找
/text():提取文本内容
/@xxx:提取属性内容
xpath的特殊用法:
1.用相同的字符开头:starts-with(@属性名称,属性字符相同部分)
html = """ <body>
<div id="test1">需要的内容1</div>
< div id = "test2" > 需要的内容2 < / div >
< div id = "test3" > 需要的内容3 < / div >
</body>"""
selector = etree.HTML(html)
content = selector.xpath('//div[starts-with(@id, "test")]/text()')
print (content)
# ['需要的内容1', '需要的内容2', '需要的内容3', ]
2.标签套标签:string(.)
html = """ <body>
<div id="test">中国</div>
<span id="ap" >
广东省
< ul>
深圳市
<li>福田区</li>
</ul>
</span>
华强北
</div>
</body>"""
selector = etree.HTML(html)
data = select.xpath('//div[@id="test"]')[0]
info = data.xpath('string(.)')
info = info.strip().replace(' ', '')
print (info)
# 中国广东省深圳市福田区华强北
3.xpath示例:
body # 选取所有body元素的所有子节点
/html # 选取根元素
body/a # 选取所有属于body的子元素的a元素
//div # 选取所有dic子元素(任意地方)
body//div # 选取所有属于body元素的后代的div元素(body下任意位置)
//@class # 选取所有名为class的属性
/body/div[1] # 选取属于body子元素的第一个div元素
/body/div[last()] # 选取属于body子元素的最后一个div元素
//div[@lang] # 选取所有拥有lang属性的div元素
//div[@lang='eng'] # 选取所有lang属性为eng的div元素
/div/* # 获取属于div元素的所有子节点
//* # 选取所有元素
//div[@*] # 获取所有带属性的div元素
/div/a|//div/p # 获取所有div的子元素a和p
//span|ul # 选取文档中的span和ul元素
body/div/p|//span # 选取所有body下的div下的p元素和所有span元素
//span[contains(@class, 'vote-post-up')] # 寻找所有属性为class的值中包含vote-post-up的span标签
八、多进程爬取
map函数,一手包办了序列操作、参数传递、结果保存等一系统操作
from multiprocessing.dummy import Pool
pool = Pool(4)
results = pool.map(爬取函数,网址列表)