scrapy的快速入门(一)

Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架。
其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
下图展示了Scrapy的大致架构,其中包含了主要组件和系统的数据处理流程(绿色箭头表示)。下面会对组件和流程进行了一个简单的解释。

Scrapy的基本流程

一、组件介绍:
1.Scrapy Engine(Scrapy引擎)
Scrapy引擎是用来控制整个系统的数据处理流程,并进行事务处理的触发。更多的详细内容可以看下面的数据处理流程。
2.Scheduler(调度程序)
调度程序从Scrapy引擎接受请求并排序列入队列,并在Scrapy引擎发出请求后返还给它们。
3.Downloader(下载器)
下载器的主要职责是抓取网页并将网页内容返还给蜘蛛(Spiders)。
4.Spiders(蜘蛛)
蜘蛛是有Scrapy用户自己定义用来解析网页并抓取制定URL返回的内容的类,每个蜘蛛都能处理一个域名或一组域名。换句话说就是用来定义特定网站的抓取和解析规则。
5.Item Pipeline(项目管道)
项目管道的主要责任是负责处理有蜘蛛从网页中抽取的项目,它的主要任务是清晰、验证和存储数据。当页面被蜘蛛解析后,将被发送到项目管道,并经过几个特定的次序处理数据。每个项目管道的组件都是有一个简单的方法组成的Python类。它们获取了项目并执行它们的方法,同时还需要确定的是是否需要在项目管道中继续执行下一步或是直接丢弃掉不处理。
项目管道通常执行的过程有:
清洗HTML数据 验证解析到的数据(检查项目是否包含必要的字段) 检查是否是重复数据(如果重复就删除) 将解析到的数据存储到数据库中
6.Middlewares(中间件)
中间件是介于Scrapy引擎和其他组件之间的一个钩子框架,主要是为了提供一个自定义的代码来拓展Scrapy的功能。

二、数据处理流程:
Scrapy的整个数据处理流程有Scrapy引擎进行控制,其主要的运行方式为:
1、引擎打开一个域名,时蜘蛛处理这个域名,并让蜘蛛获取第一个爬取的URL。
2、引擎从蜘蛛那获取第一个需要爬取的URL,然后作为请求在调度中进行调度。
3、引擎从调度那获取接下来进行爬取的页面。
4、调度将下一个爬取的URL返回给引擎,引擎将它们通过下载中间件发送到下载器。
5、当网页被下载器下载完成以后,响应内容通过下载中间件被发送到引擎。
6、引擎收到下载器的响应并将它通过蜘蛛中间件发送到蜘蛛进行处理。
7、蜘蛛处理响应并返回爬取到的项目,然后给引擎发送新的请求。
8、引擎将抓取到的项目项目管道,并向调度发送请求。
9、系统重复第二部后面的操作,直到调度中没有请求,然后断开引擎与域之间的联系。
三、安装scrapy
使用 pip 安装:
pip install Scrapy
其他平台的安装请参考官方文档
四、开始scrapy之旅
1、创建一个 Scrapy 项目
在CMD中进入一个你希望保存代码的目录,然后执行:

scrapy startproject doubanmovie250

此时项目已创建成功;


创建一个 Scrapy 项目

这个命令会在当前目录下创建一个新的目录doubanmoive,目录结构如下:

目录结构

doubanmoive250/spiders:放置spider的目录,定义提取的 Item
doubanmoive250/items.py:定义需要获取的内容字段,类似于实体类。
doubanmoive250/middlewares.py:中间件(Middleware) 下载器中间件是介入到 Scrapy 的 spider 处理机制的钩子框架,您可以添加代码来处理发送给 Spiders 的 response 及 spider 产生的 item 和 request。暂时可以先不配置,详情可查看官方文档
doubanmoive250/pipelines.py:项目管道文件,用来处理Spider抓取的数据。
doubanmoive250/settings.py:项目配置文件
2、定义提取的 Item
Item是用来装载抓取数据的容器,打开doubanmoive250/items.py可以看到默认创建了以下代码。

import scrapy


class Doubanmovie250Item(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass

我们只需要在Doubanmoive类中增加需要抓取的字段即可,如name=Field(),最后根据我们的需求完成代码如下。

import scrapy


class Doubanmovie250Item(scrapy.Item):
    # define the fields for your item here like:
   name = scrapy.Field()  # 电影名
    score = scrapy.Field()  # 豆瓣分数
    detail_url = scrapy.Field()  # 文章链接在首页爬取
    quote = scrapy.Field()  # 引用
    top = scrapy.Field()  # 排名
    year = scrapy.Field()  # 上映年份
    director = scrapy.Field()  # 导演
    scriptwriter = scrapy.Field()  # 编剧
    actor = scrapy.Field()  # 演员
    classification = scrapy.Field()  # 分类
    made_country = scrapy.Field()  # 制片国家/地区
    language = scrapy.Field()  # 语言
    showtime = scrapy.Field()  # 上映日期
    film_time = scrapy.Field()  # 片长
    alias = scrapy.Field()  # 别名
    IMDb_url = scrapy.Field()  # IMDb链接
    votes = scrapy.Field()  # 评价人数
    describe = scrapy.Field()  # 剧情简介

item的操作和dict的非常相似。
1)创建Doubanmovie250Item的对象

item= Doubanmovie250Item(year=1994, made_country='美国')

2)获取字段的值
item['year']
item.get['year']
3)设置字段的值
item['year'] = 1994
4)获取所有的键和值
获取所有的键:item.keys()
获取所有的值:item.items()
5)item复制
item2 = Doubanmovie250Item(item)
item3 = item.copy()
6)item与dict的转换
item转换为dict:dict_item = dict(item)
dict转换为item:item = Doubanmovie250Item({'year':'1994', 'made_country':'美国'})
7)可以通过继承原始的item增加或修改item;
增加:

class newitem(Doubanmovie250Item):
    short_comment = scrapy.Field()#短评

修改:

class newitem(Doubanmovie250Item):
    #增加了五星评论的百分比
    votes = scrapy.Field(Doubanmovie250Item.fields['votes'], five_star = five_star_votes)

3、编写爬取网站的 spider 并提取 Item

from scrapy.selector import Selector
from scrapy.spiders import CrawlSpider
from ..items import Doubanmovie250Item
from scrapy.http import Request
from scrapy.conf import settings #从settings文件中导入Cookie,这里也可以室友from scrapy.conf import settings.COOKIE
import random
import string
import re


class MovieSpider(CrawlSpider):
    name = "douban_movie250_spider"
    allowed_domains = ['movie.douban.com']
    start_urls = ['https://movie.douban.com/top250']
    cookie = settings['COOKIE']  # 带着Cookie向网页发请求
   
    cookies = "bid=%s" % "".join(random.sample(string.ascii_letters + string.digits, 11))
   
    def parse(self, response):

        urls = ["https://movie.douban.com/top250?start={}&filter=".format(str(25*i)) for i in range(0, 10)]
        for i, url in enumerate(urls):
            #'dont_merge_cookies': True
            yield Request(url, meta={'cookiejar': i,'dont_merge_cookies': True}, callback=self.parse_list)

    def parse_list(self, response):
        selector = Selector(response)
        infos = selector.xpath('//ol[@class="grid_view"]/li')
        #获取每页电影的部分数据,包括detail_url
        for info in infos:
            detail_url = info.xpath('div/div[2]/div[@class="hd"]/a/@href').extract()[0]
            name = info.xpath('div/div[2]/div[@class="hd"]/a/span[1]/text()').extract()[0]
            quote = info.xpath('div/div[2]/div[@class="bd"]/p/span[@class="inq"]/text()').extract()[0] if info.xpath('div/div[2]/div[@class="bd"]/p/span[@class="inq"]/text()') else "无"
            score = float(info.xpath('//span[@property="v:average"]/text()').extract()[0])
            yield Request(detail_url, meta={'cookiejar': response.meta['cookiejar'],'name': name,'detail_url': detail_url,'quote': quote,'score': score},callback=self.parse_item)

    def parse_item(self, response):
        item = Doubanmovie250Item()
        item['name'] = response.meta['name']
        item['detail_url'] = response.meta['detail_url']
        item['top'] = int(response.xpath('//span[@class="top250-no"]/text()').extract()[0][3:])
        item['score'] = response.meta['score']
        item['quote'] = response.meta['quote']
        item['year'] = int(response.xpath('//div[@id="content"]/h1/span[2]/text()').extract()[0][1:-1])
        item['director'] = response.xpath('//a[@rel="v:directedBy"]/text()').extract()
        item['scriptwriter'] = response.xpath('//div[@id="info"]/span[2]/span[@class="attrs"]/a/text()').extract()
        item['actor'] = response.xpath('//a[@rel="v:starring"]/text()').extract()
        item['classification'] = response.xpath('//div[@id="info"]/span[@property="v:genre"]/text()').extract()
        item['showtime'] = response.xpath('//div[@id="info"]/span[@property="v:initialReleaseDate"]/text()').extract()
        item['film_time'] = response.xpath('//div[@id="info"]/span[@property="v:runtime"]/text()').extract()
        item['alias'] = response.xpath('//div[@id="info"]').re(r'</span> (.+)<br>\n')[-2]
        item['IMDb_url'] = response.xpath('//div[@id="info"]/a/@href').extract()[0]
        item['votes'] = int(response.xpath('//span[@property="v:votes"]/text()').extract()[0])
        item['describe'] = response.xpath('//div[@id="link-report"]/span/text()').re(r'\S+')[0]
        check_item = response.xpath('//div[@id="info"]').re(r'</span> (.+)<br>\n')[1]
        result = self.check_contain_chinese(check_item)
        # 有些电影详情页信息包含有官方网站,比如:https://movie.douban.com/subject/1291552/
        if result:
            item['made_country'] = response.xpath('//*[@id="info"]').re(r'</span> (.+)<br>\n')[1]
            item['language'] = response.xpath('//*[@id="info"]').re(r'</span> (.+)<br>\n')[2]
        else:
            item['made_country'] = response.xpath('//*[@id="info"]').re(r'</span> (.*)<br>\n')[2]
            item['language'] = response.xpath('//*[@id="info"]').re(r'</span> (.*)<br>\n')[3]
        yield item
    #判断字符串是否含有汉字
    def check_contain_chinese(self, check_str):
        #for ch in check_str.decode('utf-8'):
        Pattern = re.compile(u'[\u4e00-\u9fa5]+')
        match = Pattern.search(check_str)
        if match:
            return True
        else:
            return False


这里要注意的点:
1)、同一时间内多次快速访问豆瓣,可能会被ban,所以这里用的是构建一个cookie池,通过每次访问豆瓣分析发现,每次的cookie都不一样,是一个11位的字母+数字的随机字符串,所以自己构建了一个cookies,每次访问时随机传入这个cookie,就不会被禁了;
2)跑完之后,发现爬到一半会有报错的,把一些特殊的情况考虑进去;通过分析报错的电影,发现有些电影的字段是不一样的,所以针对这个情况写个判断,比如:made_country、language;
3)这里我用的选择器是xpath(个人喜好,也可以css、BeautifulSoup),
xpath():返回的一个selector list列表,
extract():序列化该节点为unicode字符串并返回列表,
parse_list()方法中,其中一个参数是response,这里可以通过构建一个selector对象,调用xpath方法解析网页,也可以直接用response调用xoath方法,官方文档里有写;
4)这里调试在xoath上可能会花费很长的时间,scrapy提供了一个方式验证xpath表达式是否正确,
新建一个命令窗口,输入

scrapy shell "https://movie.douban.com/top250"

返回的response为200,然后接着输入

response.xpath('//ol[@class="grid_view"]/li')

就可以测试xpath是否正常了,返回的是Unicode格式;
这里也可以用firepath测试,可以2者结合使用;

scrapy的命令行的操作:可直接看官方文档,文档写的已经很详细的,一看就懂;

4、编写 Pipeline 来存储提取到的 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 pymongo
from scrapy.conf import settings
from scrapy.exceptions import DropItem
from scrapy import log


class DoubanMovie250Pipeline(object):
    def __init__(self):
        connection = pymongo.MongoClient(
            settings['MONGODB_SERVER'],
            settings['MONGODB_PORT']
        )
        db = connection[settings['MONGODB_DB']]
        self.collection = db[settings['MONGODB_COLLECTION']]
    def process_item(self, item, spider):
        valid = True
        for data in item:
            if not data:
                valid = False
                raise DropItem("Missing {0}!".format(data))
        if valid:
            #插入数据到mongodb数据库
            self.collection.insert(dict(item))
            log.msg("Question added to MongoDB database!",
                    level=log.DEBUG, spider=spider)
        return item

我这里是直接提取到mongodb数据库了,个人感觉用起来比较顺手,其他的也都OK,看自己习惯了。
其实每个pipeline都是一个独立的类,必须要实现process_item(self,item,spider)方法,
item对象是被爬取的item,
spider对象是被爬取改item的Spider;
所以,这个方法必须返回一个item对象,或者抛出DropItem异常,被丢弃的item将不会被pipeline执行;

定制pipeline还是不能执行的,需要激活的:
把pipeline的类添加到settings.py的ITEM_PIPELINES,

ITEM_PIPELINES = {
'douban_movie250.pipelines.DoubanMovie250Pipeline': 300,
}

ITEM_PIPELINES可以分配多个pipelines的组件,后面的数字代表了他们执行的顺序(从低到高),数字的范围是0~1000;

彩蛋:简单存储方式
使用scrapy的命令行实现快速存储的方式,存储的类型包括:json、jsonlines、csv、xml等
例如:scrapy crawl douban_movie250_spider -o movie250.csv

4、命令行启动爬虫
1)第一种方法
在爬虫代码所在目录,启动命令行,输入

scrapy crawl douban_movie250_spider

2)第二种方法
还有一种启动方法,就是在爬虫所在目前下新建一个main.py,写入以下代码,

from scrapy import cmdline
cmdline.execute("scrapy crawl douban_movie250_spider".split())

执行的时候直接运行这个文件就OK了;
3)第三种方法
在同一个进程中启动一个爬虫
使用CrawlProcess类,这个类内部将会开启Twisted reactor,配置log和设置Twist reactor自动关闭,因为Scrapy是在Twisted异步网络库上构建的,因此必须在Twisted reactor里运行。
main_spider.py启动脚本如下:

if __name__ = "__main__":
    process = CrawlProcess({'USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'})
    process.crawl(movie250)#传入的是Spider模块的文件名
    process.start()
    

或者
初始化时传入设置settings的项目信息,

if __name__ = "__main__":
    process = CrawlProcess(get_project_settings())
    process.crawl(douban_movie250_spider)#传入的是爬虫名字
    process.start()
    

运行结果:

豆瓣电影top250
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容