Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。使用框架进行数据的爬取那,可以省去好多力气,如不需要自己去下载页面、数据处理我们也不用自己去写。我们只需要关注数据的爬取规则就行,scrapy在python数据爬取框架中数据比较流行的,那么今天就用scrapy进行百度贴吧-黑中介贴吧数据的爬取。别问我为啥爬取黑中介吧的,因为我个人经历过一番。。咳咳咳,要抓住重点,咱们还是来讲怎么爬数据吧(赃官猛于虎!)。
注意:你需要自己先安装python和scrapy框架哦~
1、创建项目
scrapy startproject 自定义项目名
scrapy startproject baidutieba
该命令将会创建包含下列内容的 sqc_scapy的目录:
baidutieba/
scrapy.cfg
baidutieba/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
scrapy.cfg: 项目的配置文件
baidutieba/: 该项目的python模块。之后您将在此加入代码。
baidutieba/items.py: 项目中的item文件.
baidutieba/pipelines.py: 项目中的pipelines文件.
baidutieba/settings.py: 项目的设置文件.
baidutieba/spiders/: 放置spider代码的目录.
2、创建爬虫文件
我们要编写爬虫,首先是创建一个Spider
我们在baidutieba/spiders/目录下创建一个文件MySpider.py 。文件包含一个MySpider类,它必须继承scrapy.Spider类。同时它必须定义一下三个属性:
1、-name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
2、-start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
3、-parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(responsedata),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
创建完成后MySpider.py的代码如下
#引入文件
import scrapy
class MySpider(scrapy.Spider):
#用于区别Spider
name = "MySpider"
#允许访问的域
allowed_domains = []
#爬取的地址
start_urls = []
#爬取方法
def parse(self, response):
pass
3、定义Item
爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item 类来满足这样的需求。
Item 对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。
来,咱们先确定要爬取的数据元素
大家可以看到我们在工程目录下可以看到一个items文件,我们可以更改这个文件或者创建一个新的文件来定义我们的item。
这里,我们在同一层创建一个新的item文件Tbitems.py:
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class Tbitem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#内容
user_info = scrapy.Field()
title = scrapy.Field()
url = scrapy.Field()
short_content = scrapy.Field()
imgs = scrapy.Field()
常用方法如下:
#定义一个item
info= Tbitem()
#赋值
info['title'] = "语文"
#取值
info['title']
info.get('title')
#获取全部键
info.keys()
#获取全部值
info.items()
4、完善我的爬虫主程序1:
# coding=utf-8
#
import scrapy
from baidutieba.Tbitems import Tbitem
class MySpider(scrapy.Spider):
name = "MySpider"
allowed_domains = ['tieba.baidu.com']
start_urls = ['https://tieba.baidu.com/f?ie=utf-8&kw=%E9%BB%91%E4%B8%AD%E4%BB%8B&fr=search']
def parse(self, response):
item = Tbitem()
boxs = response.xpath("//li[contains(@class,'j_thread_list')]")
for box in boxs:
item['user_info'] = box.xpath('./@data-field').extract()[0];
item['title'] = box.xpath(".//div[contains(@class,'threadlist_title')]/a/text()").extract()[0];
item['url'] = box.xpath(".//div[contains(@class,'threadlist_title')]/a/@href").extract()[0];
item['short_content'] = box.xpath(".//div[contains(@class,'threadlist_abs')]/text()").extract()[0];
if box.xpath('.//img/@src'):
item['imgs'] = box.xpath('.//img/@src').extract()[0];
else:
item['imgs'] =[]
yield item
注:这里用到了xpath方式来获取页面信息,这里不做过多介绍,可以参考网上的xpath教程来自己学习
上面这个是利用谷歌浏览器扩展组件XPath-Helper进行的调试 组件地址:XPath-Helper_v2.0.2,当然谷歌浏览器自带了获取元素xpath路径的方法如下:
大家注意爬取的部分在MySpider类的parse()方法中进行。
parse()方法负责处理response并返回处理的数据
该方法及其他的Request回调函数必须返回一个包含 Request 及(或) Item 的可迭代的对象(yield item 具体介绍请看 彻底理解Python中的yield)
(在scrapy框架中,可以使用多种选择器来寻找信息,这里使用的是xpath,同时我们也可以使用BeautifulSoup,lxml等扩展来选择,而且框架本身还提供了一套自己的机制来帮助用户获取信息,就是Selectors。 因为本文只是为了入门所以不做过多解释。)
cd进入工程文件夹,然后运行命令行
scrapy crawl 自己定义的spidername
scrapy crawl MySpider
看以看到我们已经运行成功了 ,获取到了数据。不过那大家运行可以看到我们只爬了一页的数据,那么我们想将分页数据全部爬取那该如何做?
def parse(self, response):
item = Tbitem()
boxs = response.xpath("//li[contains(@class,'j_thread_list')]")
for box in boxs:
item['user_info'] = box.xpath('./@data-field').extract()[0];
item['title'] = box.xpath(".//div[contains(@class,'threadlist_title')]/a/text()").extract()[0];
item['url'] = box.xpath(".//div[contains(@class,'threadlist_title')]/a/@href").extract()[0];
item['short_content'] = box.xpath(".//div[contains(@class,'threadlist_abs')]/text()").extract()[0];
if box.xpath('.//img/@src'):
item['imgs'] = box.xpath('.//img/@src').extract()[0];
else:
item['imgs'] =[]
yield item
#url跟进开始
#获取下一页的url信息
url = response.xpath('//*[@id="frs_list_pager"]/a[10]/@href').extract()
if url :
page = 'https:' + url[0]
#返回url
yield scrapy.Request(page, callback=self.parse)
#url跟进结束
5、将爬取的数据进行保存
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
(1)清理HTML数据
(2)验证爬取的数据(检查item包含某些字段)
(3)查重(并丢弃)
(4)将爬取结果保存到文件或数据库中
1、将数据保存到文件里
首先那我们在项目目录下 pipelines.py同级目录建立我们的BaidutiebaPipeline.py文件
# -*- 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 sys
reload(sys)
sys.setdefaultencoding('utf-8')
import codecs
import json
from logging import log
class JsonWithEncodingPipeline(object):
'''保存到文件中对应的class
1、在settings.py文件中配置
2、在自己实现的爬虫类中yield item,会自动执行'''
def __init__(self):
self.file = codecs.open('info.json', 'w', encoding='utf-8')#保存为json文件
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"#转为json的
self.file.write(line)#写入文件中
return item
def spider_closed(self, spider):#爬虫结束时关闭文件
self.file.close()
那么这我们的数据保存到文件里的Item Pipeline就写好了,那么接下来我们想要用它就需要先注册自己的Pipeline:
在同级目录下有一个settings.py 打开文件找到ITEM_PIPELINES 注册我们的Pipeline
格式:项目目录.Pipeline文件名.Pipeline中的类名
后面int型的参数是标示执行的优先级,范围1~1000,越小越先执行
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'baidutieba.BaidutiebaPipeline.JsonWithEncodingPipeline': 300,
}
那么我们再运行、
scrapy crawl MySpider
2、将数据保存到数据库中
同样在settings.py中添加咱们的数据库保存Pipeline,并且在其中设置数据库的配置如下:
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'baidutieba.BaidutiebaPipeline.JsonWithEncodingPipeline': 300,
'baidutieba.BaidutiebaPipeline.WebcrawlerScrapyPipeline': 300,
}
# MySql 数据库链接操作
MYSQL_HOST = '127.0.0.1'
MYSQL_DBNAME = 'test' #数据库名字,请修改
MYSQL_USER = 'homestead' #数据库账号,请修改
MYSQL_PASSWD = 'secret' #数据库密码,请修改
MYSQL_PORT = 3306 #数据库端口,在dbhelper中使用
修改BaidutiebaPipeline.py文件
# -*- 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 sys
reload(sys)
sys.setdefaultencoding('utf-8')
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors
import codecs
import json
from logging import log
class JsonWithEncodingPipeline(object):
'''保存到文件中对应的class
1、在settings.py文件中配置
2、在自己实现的爬虫类中yield item,会自动执行'''
def __init__(self):
self.file = codecs.open('info.json', 'w', encoding='utf-8')#保存为json文件
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"#转为json的
self.file.write(line)#写入文件中
return item
def spider_closed(self, spider):#爬虫结束时关闭文件
self.file.close()
class WebcrawlerScrapyPipeline(object):
'''保存到数据库中对应的class
1、在settings.py文件中配置
2、在自己实现的爬虫类中yield item,会自动执行'''
def __init__(self,dbpool):
self.dbpool=dbpool
''' 这里注释中采用写死在代码中的方式连接线程池,可以从settings配置文件中读取,更加灵活
self.dbpool=adbapi.ConnectionPool('MySQLdb',
host='127.0.0.1',
db='crawlpicturesdb',
user='root',
passwd='123456',
cursorclass=MySQLdb.cursors.DictCursor,
charset='utf8',
use_unicode=False)'''
@classmethod
def from_settings(cls,settings):
'''1、@classmethod声明一个类方法,而对于平常我们见到的则叫做实例方法。
2、类方法的第一个参数cls(class的缩写,指这个类本身),而实例方法的第一个参数是self,表示该类的一个实例
3、可以通过类来调用,就像C.f(),相当于java中的静态方法'''
dbparams=dict(
host=settings['MYSQL_HOST'],#读取settings中的配置
db=settings['MYSQL_DBNAME'],
user=settings['MYSQL_USER'],
passwd=settings['MYSQL_PASSWD'],
charset='utf8',#编码要加上,否则可能出现中文乱码问题
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=False,
)
dbpool=adbapi.ConnectionPool('MySQLdb',**dbparams)#**表示将字典扩展为关键字参数,相当于host=xxx,db=yyy....
return cls(dbpool)#相当于dbpool付给了这个类,self中可以得到
#pipeline默认调用
def process_item(self, item, spider):
query=self.dbpool.runInteraction(self._conditional_insert,item)#调用插入的方法
query.addErrback(self._handle_error,item,spider)#调用异常处理方法
return item
#写入数据库中
def _conditional_insert(self,tx,item):
#print item['name']
sql="insert into test(name,url) values(%s,%s)"
print 3333333333333333333333
print item["title"]
params=(item["title"].encode('utf-8'),item["url"])
tx.execute(sql,params)
#错误处理方法
def _handle_error(self, failue, item, spider):
print '--------------database operation exception!!-----------------'
print '-------------------------------------------------------------'
print failue
这个是我给大家的一个入门的示例,如果大家有问题可以给我留言或者私信。另外由于百度贴吧的升级,可能程序抓取规则会要做相应的调整,但是主体不会变哦,大家需要自己调整下程序哦
程序完成时间:2017.7.18