Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,用于抓取web站点并从页面中提取结构化的数据。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy是一个基于Twisted,纯Python实现的爬虫框架,使用Twisted异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。整体架构大致如下:
绿线是数据流向,首先从初始URL开始,Scheduler会将其交给Downloader进行下载,下载之后会交给Spider进行分析Spider分析出来的结果有两种:一种是需要进一步抓取的链接,例如之前分析的“下一页”的链接,这些东西会被传回 Scheduler;另一种是需要保存的数据,它们则被送到Item Pipeline那里,那是对数据进行后期处理(详细分析、过滤、存储等)的地方。另外,在数据流动的通道里还可以安装各种中间件,进行必要的处理。
创建一个爬取工程
在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:
scrapy startproject fund
该命令将会创建包含下列内容的 fund 目录:
fund/
scrapy.cfg # 项目的配置文件
fund/ # 该项目的python模块
__init__.py
items.py # 相当于实体类
pipelines.py # 对Spider返回的item列表进行后续操作:过滤,保存等
settings.py # 配置文件
spiders/ # 爬取类文件夹
__init__.py
... # 这儿主要是爬取类
数据结构定义Item类
Item 是保存爬取到的数据的容器;其使用方法和python字典类似,并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
# -*- coding: utf-8 -*-
import scrapy
class FundItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
product_name = scrapy.Field() # 基金名称
product_code = scrapy.Field() # 基金代码
product_type = scrapy.Field() # 基金类型
venture_grade = scrapy.Field() # 风险等级
setup_day = scrapy.Field() # 成立日期
product_scale = scrapy.Field() # 基金规模
product_company = scrapy.Field() # 基金公司
fund_manager = scrapy.Field() # 基金经理
year_1 = scrapy.Field() # 一年 本基金/同类基金
year_2 = scrapy.Field() # 两年 本基金/同类基金
increase_last_year = scrapy.Field() # 去年的涨幅
increase_year_before_last = scrapy.Field() # 前年的涨幅
爬虫爬取Spider类
Spider 是用户编写的类, 用于从一个域(或域组)中抓取信息, 定义了用于下载的URL的初步列表, 如何跟踪链接,以及如何来解析这些网页的内容用于提取items。
要建立一个 Spider,继承 scrapy.Spider 基类,并确定三个主要的、强制的属性:
- name:爬虫的识别名,它必须是唯一的,在不同的爬虫中你必须定义不同的名字.
- start_urls:包含了Spider在启动时进行爬取的url列表。因此,第一个被获取到的页面将是其中之一。后续的URL则从初始的URL获取到的数据中提取。我们可以利用正则表达式定义和过滤需要进行跟进的链接。
- parse():是spider的一个方法。被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
parse()是scrapy默认的解析html的回调方法,当然可以指定这儿的方法名并自己实现,负责解析返回的数据、匹配抓取的数据(解析为item)并跟踪更多的URL。
使用Item返回抓取的值
# -*- coding: utf-8 -*-
import scrapy
from scrapy.selector import Selector
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from fund.items import FundItem
class FundSpider(CrawlSpider):
start_urls = []
for i in range(1,19):
start_urls.append("https://list.lu.com/list/fund?subType=&haitongGrade=4&fundGroupId=¤tPage="+str(i)+"&orderType=this_year_increase_desc&searchWord=#sortTab")
# 定义spider名字的字符串(string),一般取值为站点的域名,如xiaoyu.com -> xiaoyu
name = "fund"
#設置延時
download_delay = 1
# 包含了spider允许爬取的域名(domain)列表(list)
# 当 OffsiteMiddleware 启用时, 域名不在列表中的URL不会被跟进。
allowed_domains = ["list.lu.com"]
# 当没有制定特定的URL时,spider将从该列表中开始进行爬取
# start_urls = ["https://list.lu.com/list/fund?subType=&haitongGrade=&fundGroupId=¤tPage=1&orderType=one_month_increase_desc&searchWord=#sortTab"]
rules = (
#将所有符合正则表达式的url加入到抓取列表中
Rule(LinkExtractor(allow=(r"https://list.lu.com/list/fund?subType=&haitongGrade=&fundGroupId=¤tPage=\d+&orderType=one_month_increase_desc&searchWord=#sortTab",))),
#将所有符合正则表达式的url请求后下载网页代码, 形成response后调用自定义回调函数
Rule(LinkExtractor(allow=(r"\S+productDetail\S+",)),callback="parse_item"),
)
def parse_item(self,response):
#self.log("======>>>>>>:" + response.url)
sel = Selector(response)
item = FundItem()
# 通过css来获取值
item['product_name'] = sel.css('div[class*=product-name]::text').extract()[0].strip().encode('utf8')
item['product_code'] = sel.css('div[class*=product-code]::text').extract()[0].strip().encode('gbk').split(":")[1].decode("gbk").encode("utf8")
# 通过xpath来获取值,也可以使用正则表达式来获取值
item['product_type'] = sel.xpath('//ul[@class="fund-info clearfix"]/li[4]/b/text()').extract()[0].strip().encode('utf8')
item['venture_grade'] = sel.xpath('//div[@class="venture-grade"][1]/span/text()').extract()[0].strip().encode("utf8")
item['setup_day'] = sel.xpath('//ul[@class="fund-info clearfix"]/li[8]/b/text()').extract()[0].strip().encode('utf8')
item['product_scale'] = sel.xpath('//ul[@class="fund-info clearfix"]/li[10]/b/text()').extract()[0].strip().encode('gbk')[:-4].decode("gbk").encode("utf8")
item['product_company'] = sel.xpath('//ul[@class="fund-info clearfix"]/li[3]/b/text()').extract()[0].strip().encode('utf8')
item['fund_manager'] = sel.xpath('//p[@class="manager-icon"]/span/text()').extract()[0].strip().encode('utf8')
# 计算每年的成长与同类比较值
self1 = sel.xpath('//table[@class="product-table phase-increase-table"]/tbody/tr[1]/td[5]/span/text()').extract()[0].strip().encode('gbk')[:-1].decode("gbk").encode("utf8")
self2 = sel.xpath('//table[@class="product-table phase-increase-table"]/tbody/tr[1]/td[6]/span/text()').extract()[0].strip().encode('gbk')[:-1].decode("gbk").encode("utf8")
other1 = sel.xpath('//table[@class="product-table phase-increase-table"]/tbody/tr[2]/td[5]/span/text()').extract()[0].strip().encode('gbk')[:-1].decode("gbk").encode("utf8")
other2 = sel.xpath('//table[@class="product-table phase-increase-table"]/tbody/tr[2]/td[6]/span/text()').extract()[0].strip().encode('gbk')[:-1].decode("gbk").encode("utf8")
item["year_1"] = float(self1) / float(other1)
item["year_2"] = float(self2) / float(other2)
item["increase_last_year"] = float(self1)
item["increase_year_before_last"] = float(self2) - float(self1)
return item
使用ItemLoader返回抓取的值
# -*- coding: utf-8 -*-
import scrapy
from scrapy.spider import Spider
from scrapy.selector import Selector
from scrapy.contrib.loader import ItemLoader
from lu.items import LuItem
class LuSpider(Spider):
# 定义spider名字的字符串(string),一般取值为站点的域名,如xiaoyu.com -> xiaoyu
name = "lu"
#設置延時
download_delay = 2
# 包含了spider允许爬取的域名(domain)列表(list)
# 当 OffsiteMiddleware 启用时, 域名不在列表中的URL不会被跟进。
allowed_domains = ["lu.com"]
# 当没有制定特定的URL时,spider将从该列表中开始进行爬取
start_urls = ["https://list.lu.com/list/fund"]
#start_urls = ["https://list.lu.com/list/productDetail?productId=2278857"]
# 当response没有指定回调函数时,parse方法是Scrapy处理下载的response的默认方法
# 负责处理response并返回处理的数据以及(/或)跟进的URL
def parse(self,response):
self.log(">>>>>>:" + response.url) # 将抓取的URL保存到log文件中
''' 将抓取到的页面保存到文件中
filename = response.url.split("=")[1] + ".html" #基金的代码作为名称
with open(filename, 'wb') as f:
f.write(response.body)
'''
sel = Selector(response)
for url in sel.css('a[class*=project-name]::attr("href")').extract():
self.log("======>>>>>>:" + url)
newUrl = "https://list.lu.com" + url
yield scrapy.Request(newUrl, callback=self.parseItem)
def parseItem(self,response):
#self.log(".........:" + response.url) # 将抓取的URL保存到log文件中
l = ItemLoader(item=LuItem(),response=response)
# 通过css来获取值
l.add_css("product_name",'div[class*=product-name]::text')
l.add_css("product_code",'div[class*=product-code]::text')
# 通过xpath来获取值,也可以使用正则表达式来获取值
l.add_xpath("product_type",'//ul[@class="fund-info clearfix"]/li[4]/b/text()')
l.add_xpath("venture_grade",'//div[@class="venture-grade"][1]/span/text()')
l.add_xpath("setup_day",'//ul[@class="fund-info clearfix"]/li[8]/b/text()')
l.add_xpath("product_scale",'//ul[@class="fund-info clearfix"]/li[10]/b/text()')
l.add_xpath("haitong_grade",'//table[@class="grade-table"]/tbody/tr[1]/td[2]/i/@class')
l.add_xpath("shangzheng_grade",'//table[@class="grade-table"]/tbody/tr[2]/td[2]/i/@class')
l.add_xpath("yinghe_grade",'//table[@class="grade-table"]/tbody/tr[3]/td[2]/i/@class')
# 直接赋值
sel = Selector(response)
procuct_company = sel.xpath('//ul[@class="fund-info clearfix"]/li[3]/b/text()').extract()
fund_manager = sel.xpath('//p[@class="manager-icon"]/span/text()').extract()
l.add_value("procuct_company",procuct_company)
l.add_value("fund_manager",fund_manager)
return l.load_item()
#sel = Selector(response)
# css选择器 获取div中的class为product-name的值,序列化之后取出list的第一个值,
# 并进行去空格操作,然后进gbk解码,从而获取到基金名称
#print sel.css('div[class*=product-name]::text').extract()[0].strip().encode('gbk')
#print sel.css('div[class*=product-code]::text').extract()[0].strip().encode('gbk')
pipelines类
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
这儿可以做如下处理:清理HTML数据,验证爬取的数据(检查item包含某些字段),查重(并丢弃),保存数据到数据库等操作
这儿主要实行process_item()方法,主要是这儿进行数据的处理!
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import MySQLdb
# 数据库链接方法
def ConnectDB():
conn = MySQLdb.connect(host='localhost',user='root',passwd='xiaode',db='spider',port=3306,charset='utf8')
return conn
class FundPipeline(object):
def process_item(self, item, spider):
#for key in item:
#print key,":",item[key]
# 链接数据库 并存入数据库中
conn = ConnectDB()
cur = conn.cursor()
cur.execute("select product_code from fund where product_code = %s",(item['product_code'],))
result = cur.fetchone()
if result is None:
cur.execute("insert into fund (product_code,product_name,product_type,venture_grade,setup_day,product_scale,product_company,fund_manager,year_1,year_2,increase_last_year,increase_year_before_last) \
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",(item['product_code'],item['product_name'],item['product_type'],item['venture_grade'],item['setup_day'],str(item['product_scale']),item['product_company'],item['fund_manager'],str(item['year_1']),str(item['year_2']),str(item["increase_last_year"]),str(item["increase_year_before_last"])))
print str(item['product_code'])+"记录插入"
else:
cur.execute("update fund set product_name=%s,product_type=%s,venture_grade=%s,setup_day=%s,product_scale=%s,product_company=%s,fund_manager=%s,year_1=%s,year_2=%s,increase_last_year=%s,increase_year_before_last=%s where \
product_code = %s",(item['product_name'],item['product_type'],item['venture_grade'],item['setup_day'],str(item['product_scale']),item['product_company'],item['fund_manager'],str(item['year_1']),str(item['year_2']),str(item['increase_last_year']),str(item['increase_year_before_last']),item['product_code']))
print str(item['product_code'])+"记录更新"
conn.commit()
conn.close()
return item
项目配置文件
Scrapy设定(settings)提供了定制Scrapy组件的方法。您可以控制包括核心(core),插件(extension),pipeline及spider组件。
# -*- coding: utf-8 -*-
# Scrapy settings for fund project
#
# For simplicity, this file contains only the most important settings by
# default. All the other settings are documented here:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
#
BOT_NAME = 'fund'
SPIDER_MODULES = ['fund.spiders']
NEWSPIDER_MODULE = 'fund.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'fund (+http://www.yourdomain.com)'
# 编写完pipeline后,为了能够启动它,必须将其加入到ITEM_PIPLINES配置
ITEM_PIPELINES = {
'fund.pipelines.FundPipeline': 1, #后面数值越小,执行的优先级越大
}
运行项目
使用如下的命令运行项目进行爬取:
scrapy crawl fund
这样就可以爬取网站并保存数据到数据库中,保存后的数据截图如下: