Scrapy学习笔记(1)

要学习自然语言处理,必须要有的就是语料库(corpus),除了公开的语料库以外,如果要对特定的内容进行分析,就需要自己准备数据。不出意外的话,要想获得大量的文本预料数据,就只能使用网络爬虫进行爬取。因此,这几天开始学习网络爬虫,看网上很多说法,要学习各种各样的网络协议,对我个人而言,主要是为了快速掌握一门知识,因此就直接从最快的东东下手了——Scrapy——一个异常成熟的网络爬虫框架。说它是框架,是因为里面已经准备好了各种各样的工具,如认证、双向爬取等等,在框架中的特定位置填充好代码后,只需要在终端中运行一条条命令的,就可以进行爬取了。下面我们来看看Scrapy时怎样爬取网络数据的。

需要说明的是,本文的代码都是在Python3下完成的。

1.XPath

众所周知,在HTML中所有的数据都是以树的形式保存数据的,要想抓取特定位置的数据,就需要使用XPath。XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。

如果想要测试XPath,可以使用Chrome浏览器的开发者工具(Developers Tools)-Console来直接测试XPath语句的正确性。具体使用方式如下:

$x('<XPath表达式>')

关于XPath的语法,其实也非常简单,我们一般用到的主要有三种,如下表所示:

表达式 描述
/ 从根节点选取特定类型的元素
// 从任意位置选取特定类型的元素
@ 所要选取的节点属性

看了这张表,可能还未必完全理解,需要举几个例子,以example这个网站为例,其HTML代码如下:

<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 50px;
        background-color: #fff;
        border-radius: 1em;
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        body {
            background-color: #fff;
        }
        div {
            width: auto;
            margin: 0 auto;
            border-radius: 0;
            padding: 1em;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is established to be used for illustrative examples in documents. You may use this
    domain in examples without prior coordination or asking for permission.</p>
    <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

如果需要选取<div>中的<h1>元素,就可以使用如下代码:

代码1:

/html/body/div/h1

代码2:

//div/h1

上面两段代码都能实现刚才提到的功能,其中第一段代码使用了从根开始的定位方法,而第二段代码则使用了双斜杠,意味着在整个文档中查找所有div,然后在其子节点中寻找h1节点。使用上述代码后,得到的都是:

<h1>Example Domain</h1>

如果想要直接获得h1标签内的内容,则需要使用

//div/h1/text()

如果有多个h1标签,而我们又只想获得某一个,则可以使用

//div/h1[index]

其中index为第i个元素的索引,需要注意的是,索引是从1开始计算

如果想要获得某个标签的特定属性,例如我们想要获得链接的href属性,则可以

//a/@href

当然,XPath的代码远不止上面这么简单,我们再来看几个例子,以下例子均来自于《精通Python爬虫框架Scrapy》

#获得id为'firstHeading'的h1标签中所包含的文字信息
//h1[@id="firstHeading"]/text() 

# 获取id为toc的div标签内的无序列表(ul)中的所有链接的URL
//div[@id="toc"]/ul//a/@href

# 获取class属性为ltr以及skin-vector的任意元素内,所有的h1中的文本
//*[contains(@class, "ltr") and contains(@class, "skin-vector")]//h1//text()

2. Scrapy

2.1 Scrapy Shell

Scrapy Shell 是Scrapy框架提供的一个能够爬取单一页面的工具,能够有效帮助我们调试程序遇到的各类问题。在此,我们先给出几个小例子,以百度百科为例吧:

scrapy shell -s USER_AGENT="Mozilla/5.0" https://baike.baidu.com/item/%E5%BB%B6%E7%A6%A7%E6%94%BB%E7%95%A5/20481391#hotspotmining

执行上述代码后:


Scrapy Shell

可以看到,已经成功获得了网页信息,如果要测试XPath,就可以使用如下代码:

response.xpath('//dd[@class="lemmaWgt-lemmaTitle-title"]/h1/text()').extract()

用上述代码可以成功获得网页的标题信息

['延禧攻略']

此外,还可以使用css函数,直接选择对应的元素

response.css('.lemmaWgt-lemmaTitle-title').xpath('//h1/text()').extract()

如果要退出Scrapy Shell,则需要使用Ctrl+D

但是对某些网页执行scrapy shell的时候(例如万方),会无法进入shell的交互模式,不知道为什么,目前我怀疑和网页的加载方式有关。

2.2 Scrapy框架

从这里开始,我们正式进入Scrapy的框架学习。

2.2.1 建立项目

要想生成一个Scrapy项目,只需要使用如下代码:

scrapy startproject <your project name>

scrapy startproject

使用Tree命令后,可以看到生成之后的目录结构:
目录结构

其中item.py中定义了我们将要收集的信息,例如我们可能需要采集"北京大学"中的标题信息、位置信息以及描述信息(请原谅,我又换了一个例子,其他的网站都有反爬机制),那么这些就可以通过在items.py中定义一个类来描述:

from scrapy.item import Item, Field

class PkuItem(Item):
    title = Field()
    date = Field()

2.2.2 真正的爬虫

此时,整个框架中还没有爬虫,只是定义了我们的数据内容,要想编写爬虫,同样需要使用我们的框架,在my_scrapy_project的根目录下运行如下命令:

scrapy genspider [options] <name> <domain>

其中的options包括


genspider options

例如,我们如果要生成一个名为pku(北京大学)的网络爬虫,就可以使用如下代码:

scrapy genspider pku pku.edu.cn

执行成功后,会看到爬虫的目录发生了变化——在spiders文件夹下多了一个dzdp.py文件。该文件就是我们需要编写爬虫的地方

生成爬虫

其代码模板会生成如下:

import scrapy

class PkuSpider(scrapy.Spider):
    name = "pku"
    allowed_domains = ["pku.edu.cn"]
    start_urls = ['http://news.pku.edu.cn']

    def parse(self, response):
        pass

其中allowed_domains表示允许爬取的域名范围,start_urls中存储的是我们要爬取的网页地址,我们先将其进行简单的修改,只爬取一个网页http://news.pku.edu.cn/xwzh/2018-08/28/content_304033.htm

parse函数是我们需要编写的部分,可以很清楚的看到parse函数中的参数response,这是我们在shell中已经见到过的,我们先将其修改为最简单的一种写法:

import scrapy
from my_scrapy_project.items import PkuItem

class PkuSpider(scrapy.Spider):
    name = "pku"
    allowed_domains = ["pku.edu.cn"]
    start_urls = ['http://news.pku.edu.cn/xwzh/2018-08/26/content_304015.htm']

    def parse(self, response):
        item = PkuItem()
        item['title'] = response.css('.con18').xpath('text()').extract()
        item['date'] = response.css('.be12').xpath('text()').re('\d{4}-\d{2}-\d{2}')
        return item

编写好之后就可以运行爬虫了,运行爬虫可以直接在shell中运行:

scrapy crawl pku

执行之后的结果如下图所示,可以从中看出我们已经在网站中获得了想要的信息。


此外,还可以使用ItemLoader来简化上述代码:

# -*- coding: utf-8 -*-
import scrapy
from my_scrapy_project.items import PkuItem
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose

class PkuSpider(scrapy.Spider):
    name = "pku"
    allowed_domains = ["www.pku.edu.cn/"]
    start_urls = ['http://news.pku.edu.cn/xwzh/2018-08/26/content_304015.htm']

    def parse(self, response):
        l = ItemLoader(item = PkuItem(), response = response)
        l.add_xpath('title', '//td[@class="con18"]/text()', MapCompose(str.strip))
        l.add_xpath('date', '//*[@class="be12"]/text()', MapCompose(str.strip), re='\d{4}-\d{2}-\d{2}')
        return l.load_item()

当然,我们还可以将爬取到的数据直接存储为csv、json等格式:

scrapy crawl pku -o items.json
scrapy crawl pku -o items.csv
scrapy crawl pku -o items.jl
scrapy crawl pku -o items.xml

2.2.3 爬取多个URL

在之前的例子中,我们只是爬取了http://news.pku.edu.cn/xwzh/2018-08/26/content_304015.htm一个网址,如果我们想爬取多个网址的话,就需要使用新的方法。这次我们要爬取的对象是北大新闻网,对于这样一个网站,网络爬虫主要有两个爬取方向:

  • 横向爬取 所谓横向就是从一个列表页面跳转到另一个列表页面
  • 纵向爬取 所谓纵向爬取是指从一个列表页面提取出每个详情页面

要实现横向爬取,首先需要获取下一页xPath,通过观察,我们将北大新闻网站上的下一页的xpath设置为//a[@class="be12"]/@href;而纵向爬取时,每个新闻的xpath可以使用//table[@id="nav2_7Tabcontent_10"]//a/@href

在确定了需要爬取的对象之后,我们就可以名正言顺的开始编写爬虫的代码了。首先我们将之前编写的parse函数重新命名为parse_item,之后在重新编写parse函数,parse函数的代码如下:

# -*- coding: utf-8 -*-
import scrapy
from my_scrapy_project.items import PkuItem
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose
from scrapy.http import Request
import urllib


class PkuSpider(scrapy.Spider):
    name = "pku"
    allowed_domains = ["pku.edu.cn"]
    start_urls = ['http://news.pku.edu.cn/xwzh/xwzh.htm']

    # 解析函数
    def parse(self, response):
        next_selector = response.xpath('//a[@class="be12"]/@href').extract()
        for url in next_selector:
            yield Request(response.urljoin(url))

        item_selector = response.xpath(
            '//table[@id="nav2_7Tabcontent_10"]//a/@href').extract()
        for url in item_selector:
            yield Request(response.urljoin(url), callback=self.parse_item)

    # 原parse函数
    def parse_item(self, response):
        l = ItemLoader(item=PkuItem(), response=response)
        l.add_xpath(
            'title', '//td[@class="con18"]/text()', MapCompose(str.strip))
        l.add_xpath('date', '//*[@class="be12"]/text()',
                    MapCompose(str.strip), re='\d{4}-\d{2}-\d{2}')
        return l.load_item()

2.2.4 爬取多个URL的另一种实现方式

同样是爬取多个URL,除了使用上面的方法以外,还可以更改爬虫的模板。默认情况下,使用genspider命令时,会使用默认的模板basic来生成爬虫,如果要想查看可用的模板,可以用如下命令

scrapy genspider --list

能够看到,scrapy提供了四种模板

Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

这次,我们来使用crawl来实现上面的双向爬取的功能。首先,以模板crawl来生成爬虫:

scrapy genspider -t crawl easy_pku pku.edu.cn

此时,在spiders文件夹下,会生成一个easy_pku.py的文件:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class EasyPkuSpider(CrawlSpider):
    name = 'easy_pku'
    allowed_domains = ['pku.edu.cn']
    start_urls = ['http://pku.edu.cn/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        i = {}
        #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
        #i['name'] = response.xpath('//div[@id="name"]').extract()
        #i['description'] = response.xpath('//div[@id="description"]').extract()
        return i

其中绝大部分与之前的pku爬虫相同,最重要的不同在于亮点:

  • 继承自不同的类,EasyPkuSpider继承自CrawlSpider
  • 多了一个rules
    可以预见的是,要实现双向爬取,就需要在rules上面做文章了。我们先将start_urls设置为起始页面http://news.pku.edu.cn/xwzh/xwzh.htm,然后将前面编写的解析每个详情页面的函数,放到parse_item函数中,最后修改rules
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.loader.processors import MapCompose
from my_scrapy_project.items import PkuItem
from scrapy.loader import ItemLoader

class EasyPkuSpider(CrawlSpider):
    name = 'easy_pku'
    allowed_domains = ['pku.edu.cn']
    start_urls = ['http://news.pku.edu.cn/xwzh/xwzh.htm']

    rules = (
        Rule(LinkExtractor(restrict_xpaths='//*[contains(@class, "be12")]')),
        Rule(LinkExtractor(restrict_xpaths='//table[@id="nav2_7Tabcontent_10"]//*'), callback='parse_item'),
    )

    def parse_item(self, response):
        l = ItemLoader(item=PkuItem(), response=response)
        l.add_xpath(
            'title', '//td[@class="con18"]/text()', MapCompose(str.strip))
        l.add_xpath('date', '//*[@class="be12"]/text()',
                    MapCompose(str.strip), re='\d{4}-\d{2}-\d{2}')
        return l.load_item()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容