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()  

如上:咱们建立了Tbitems容器来保存抓取的信息,user_info 对应发帖人信息 ,title帖子标题,url帖子详情地址,short_content帖子的简短介绍,imgs帖子的图片

常用方法如下

#定义一个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教程来自己学习

file

上面这个是利用谷歌浏览器扩展组件XPath-Helper进行的调试 组件地址:XPath-Helper_v2.0.2,当然谷歌浏览器自带了获取元素xpath路径的方法如下:

file

大家注意爬取的部分在MySpider类的parse()方法中进行。 parse()方法负责处理response并返回处理的数据

该方法及其他的Request回调函数必须返回一个包含 Request 及(或) Item 的可迭代的对象(yield item 具体介绍请看 彻底理解Python中的yield

(在scrapy框架中,可以使用多种选择器来寻找信息,这里使用的是xpath,同时我们也可以使用BeautifulSoup,lxml等扩展来选择,而且框架本身还提供了一套自己的机制来帮助用户获取信息,就是Selectors。 因为本文只是为了入门所以不做过多解释。)

cd进入工程文件夹,然后 运行命令行

scrapy crawl 自己定义的spidername

scrapy crawl MySpider  

file

看以看到我们已经运行成功了 ,获取到了数据。不过那大家运行可以看到我们只爬了一页的数据,那么我们想将分页数据全部爬取那该如何做?

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跟进结束  

可以看到url跟进 和for同级 也就是说 for循环完成后(即本页面数据抓取完成后)进行下一页的爬取,获取到下一页按钮的地址 然后作为一个Request进行了可迭代的数据返回这样就可以进行分页数据的爬取了

file

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中使用  

file

修改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  

file

这个是我给大家的一个入门的示例,如果大家有问题可以给我留言或者私信。另外由于百度贴吧的升级,可能程序抓取规则会要做相应的调整,但是主体不会变哦,大家需要自己调整下程序哦

程序完成时间:2017.7.18

程序代码