Python:Scrapy(二) 实例分析与总结、写一个爬虫的一般步骤

学习自:Scrapy爬虫框架教程(二)-- 爬取豆瓣电影TOP250 - 知乎

Python Scrapy 爬虫框架实例(一) - Blue·Sky - 博客园

1、声明Item

爬虫爬取的目标是从非结构性的数据源提取结构性的数据,例如网页。Spider可以以Dict类型来返回提取的数据。然而,虽然Dict很方便,但是缺少结构性,容易打错字段的名字或者返回不一致的数据,特别是用在具有多个Spider的大项目中。

为了定义常用的输出数据,Scrapy提供了Item类。Item对象是种简单的容器,保存了爬取到的数据。其提供了类似Dict的API以及用于声明可用字段的简单语法。许多Scrapy组件使用了Item提供的额外信息——根据Item声明的字段来导出数据、序列化可以通过Item定义的元数据、追踪Item实例来帮助寻找内存泄漏等等。

 

Item使用简单的class定义语法以及Field对象来声明。我们打开之前创建的项目下的items.py文件,写入以下代码声明Item

复制代码
import scrapy


class DoubanMovieItem(scrapy.item):
    # 排名
    ranking = scrapy.Field()
    # 名称
    name = scrapy.Field()
    # 评分
    score = scrapy.Field()
    # 评论人数
    num = scrapy.Field()
复制代码

2、爬虫程序

在spiders目录下创建爬虫文件douban_spider.py,并写入初步的代码:

复制代码
from scrapy.spiders import Spider
from S.items import DoubanMovieItem

class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        item=DoubanMovieItem()
复制代码

这是一个基本的Spider的Model,首先我们要导入scrapy.spiders中的Spider类,以及我们刚刚定义好的scrapyspider.items中的DoubanMovieItem类。接着创建我们自己的Spider类DoubanMovieTop250Spider,该类继承自Spider类。scrapy.spiders中有许多不同的爬虫类可以供我们继承,一般情况下使用Spider类就可以满足要求。

3、提取网页信息

我们使用XPath语法来提取我们所需的信息。

首先我们在chrome浏览器中进入豆瓣电影TOP250页面并打开开发者工具。点击工具栏左上角的类鼠标图标或者CTRL + SHIFT + C在页面中点击我们想要的元素就可以看到它的HTML源码的位置。一般抓取的时候会以先抓大再抓小的原则来抓取。通过观察我们看到该页面的所有影片的信息都位于一个class属性为grid_viewol标签的il标签内。

复制代码
<ol class="grid_view">
        <li>
            <div class="item">
                <div class="pic">
                    <em class="">1</em>
                    <a href="https://movie.douban.com/subject/1292052/">
                        <img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class="">
                    </a>
                </div>
                <div class="info">
                    <div class="hd">
                        <a href="https://movie.douban.com/subject/1292052/" class="">
                            <span class="title">肖申克的救赎</span>
                                    <span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
                                <span class="other">&nbsp;/&nbsp;月黑高飞(港)  /  刺激1995(台)</span>
                        </a>


                            <span class="playable">[可播放]</span>
                    </div>
                    <div class="bd">
                        <p class="">
                            导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
                            1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
                        </p>

                        
                        <div class="star">
                                <span class="rating5-t"></span>
                                <span class="rating_num" property="v:average">9.7</span>
                                <span property="v:best" content="10.0"></span>
                                <span>2317522人评价</span>
                        </div>

                            <p class="quote">
                                <span class="inq">希望让人自由。</span>
                            </p>
                    </div>
                </div>
            </div>
        </li>
……
</ol>
复制代码

因此我们根据以上原则对所需信息进行抓取

复制代码
from scrapy.spiders import Spider
from S.items import DoubanMovieItem


class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        item = DoubanMovieItem()
        movies=response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(r'(\d+)人评价')[0]
            yield item
复制代码

对于Scrapy提取页面信息的内容详情可以参照官方文档

4、运行爬虫

在项目文件夹内打开cmd运行一下命令

scrapy crawl douban_movie_top250 -o douban.csv

 注意此处的douban_movie_top250即我们刚刚写的爬虫的name,而-o douban.csv是scrapy提供的将Item输出位CSV格式的快捷方式。

运行时可能会出现403错误,这是因为豆瓣对于爬虫设置了门槛,我们需要在发送请求时附加请求头headers'User-Agent'项,修改后的代码如下:

复制代码
from scrapy import Request
from scrapy import Spider
from S.items import DoubanMovieItem

class DoubanMovieTop250Spider(Spider):
    name='douban_movie_top250'
    headers={
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_requests(self):
        url='https://movie.douban.com/top250'
        yield Request(url,headers=self.headers)

    def parse(self,response):
        item=DoubanMovieItem()
        movies=response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['score_num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(ur'(\d+)人评价')[0]
            yield item
复制代码

更改后的代码中并没有start_urls,而又多了start_requests,二者的作用可以见Scrapy(一)。简单来说使用start_requests使我们对于初始URL处理有了更多的权利,比如这次给初始URL增加请求头User-Agent

再次运行爬虫,就可以看到我们需要的信息都被下载到douban.csv中了。

5、自动翻页

然而我们只能爬取到该页的内容,那么如何把剩下的也一起爬下来呢?实现自动翻页一般有两种方法:

①、在页面中找到下一页的地址;

②、根据URL变化规律构造所有页面地址。

一般情况下采用第一种方法,第二种方法适用于下一页地址为JS加载的情况。今天我们只说第一种方法。

 

首先,我们利用Chrome开发者模式找到下一页的地址:

<span class="next">
            <link rel="next" href="?start=25&amp;filter=">
            <a href="?start=25&amp;filter=">后页&gt;</a>
        </span>

然后在解析该页面时获取下一页的地址并将地址交给Scheduler,方法,在上文代码中的parse方法最后补充语句:

        next_url=response.xpath('//span[@class="next"]/a/@href').extract()
        if next_url:
            next_url = 'https://movie.douban.com/top250' + next_url[0]
            yield Request(next_url, headers=self.headers)

最终完成的代码为:

复制代码
from scrapy import Request
from scrapy import Spider
from S.items import DoubanMovieItem

class DoubanMovieTop250Spider(Spider):
    name='douban_movie_top250'
    headers={
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_requests(self):
        url='https://movie.douban.com/top250'
        yield Request(url,headers=self.headers)

    def parse(self,response):
        item=DoubanMovieItem()
        movies=response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath(
                './/div[@class="pic"]/em/text()').extract()[0]
            item['name'] = movie.xpath(
                './/div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath(
                './/div[@class="star"]/span[@class="rating_num"]/text()'
            ).extract()[0]
            item['num'] = movie.xpath(
                './/div[@class="star"]/span/text()').re(r'(\d+)人评价')[0]
            yield item

        next_url=response.xpath('//span[@class="next"]/a/@href').extract()
        if next_url:
            next_url = 'https://movie.douban.com/top250' + next_url[0]
            yield Request(next_url, headers=self.headers)
复制代码

 

6、总结

构建Scrapy爬虫的一般性语句

①创建项目、爬虫

scrapy startproject xxx
cd xxx
scrapy genspider xxxs url

items.py:为提取要素创建承接变量

每个要提取的要素都要有一个承接对象,均是在Item类下通过scrapy.Field()方法创建:

复制代码
#items.py
import scrapy

class xxx_Item(scrapy.item):

        attr1=scrapy.Field()

        attr2=scrapy.Field()

        ……
复制代码

setting.py:一些配置

主要设置项有以下三个,具体用途见Scrapy(一)中所写

ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
}

pipelines.py:管道

当需要多个管道的时候,在这个文件夹中添加(一般用不到)

spiders/xxx.py:爬虫

step1、导入模块、类

from scrapy import Request
from scrapy import Spider
from xxx.items import xxx_Item

step2、spider类属性

class TxsSpider(scrapy.Spider):
    name = 'txs'
    allowed_domains = ['v.qq.com']
    start_urls = ['https://v.qq.com/channel/movie']

其实这一部分,在通过genspider创建爬虫文件时已经自动生成了,唯一需要我们写的是start_urls,这是我们进行爬取的起始页

step3、spider类方法

主要是parse,在其中通过XPath提取所需项:

复制代码
def parse(self,response):
    item=xxx_Item()    #items.py引入的Item类
    movies=response.xpath('...')    #提取所有的影片 xpath函数参数即为xpath路径表达式
    
    for movie in movies:
        item['attr_1'] = movie.xpath('.//...').extract()[0] #注意路径开头的点. !!!
        item['attr_2'] = movie.xpath('.//...').extract()[0]
        ...
        #如果只需要提取部分文本,除了在xpath中用text()函数外,还要用正则表达式匹配
        item['attr_n'] = movie.xpath('.//.../text()').re(r'...')
        yield item    #每提取一个item,就要记录一个,用yield item构造迭代器

   #如果需要翻页,就用下文的代码进行翻页
  #方法①,直接通过页面中展示出来的“下一页”链接进行
   #链接要素一般在属性href中
    next_url=response.xpath('.../@href').extract()
    if next_url: #如果next_url存在时,就根据页码变换公式构造下页URL
        next_url= '基本url' + next_url[0]
        yield Request(next_url,headers=self.headers)#请求下一页URL,重复爬取直至结束


  #方法②,通过观察每一页的URL,发现其中规律,根据规律构造URL
    if self.offset < 120:
            self.offset += 30
            next_url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30'.format(
                str(self.offset))
            yield Request(url=url,header=self.header)
复制代码

 

运行爬虫

scrapy crawl 爬虫名 -o 文件     #追加写
scrapy crawl 爬虫名 -0 文件     #覆盖写 大写字母而非数字

将爬取到的内容(即⑤中的item)保存为csv文件

 

 


 

如果按照以上①-⑥执行,就可以基本实现爬虫功能;如果想进一步优化,还有其他一些可选项,优化项见⑦-⑨

pipelines.py:管道

管道可以处理提取的数据,比如存数据库等,这里只展示如何进行输出:

from itemadapter import ItemAdapter
class TxPipeline:
    def process_item(self, item, spider):
        print (item)
        return item

处理项是item,只需在process_item中写对item进行处理的代码就行了。

在Python中运行;在Python中写代码,使之可以起到与在cmd运行代码相同的作用:

from scrapy import cmdline

cmdline.execute('scrapy crawl txs -o tx.csv'.split())

 

多线程爬取

以上所说爬取过程,都是顺序执行的,即总是前一页先输出,后一页后输出。不适合处理数据量较大的情况,一个好的方式是采用多线程方法,这里的多线程是基于方法的多线程,不是通过创建Thread对象来实现。具体说来,是一次性请求交给Scheduler

我们通过重写start_requests方法来实现我们的想法:

 

复制代码
#txs.py

def start_requests(self):
        for i in range(4):#假设要爬取4页
            url=new_url(i)#根据各页URL规律构建各页的URL
            yield Request(url,callback=self.parse)

def parse(self,response):
        #删去翻页查询的代码,其他照旧
复制代码

 

所以多线程查询的代码,总的应该为:

复制代码
def start_requests(self):
     for i in range(4):
         url=new_url(i)    #每页URL的构造
         yield Request(url,callback=self.parse)

def parse(self,response):
    item=xxx_Item()    #items.py引入的Item类
    movies=response.xpath('...')    #提取所有的影片 xpath函数参数即为xpath路径表达式
    
    for movie in movies:
        item['attr_1'] = movie.xpath('.//...').extract()[0] #注意路径开头的点. !!!
        item['attr_2'] = movie.xpath('.//...').extract()[0]
        ...
        #如果只需要提取部分文本,除了在xpath中用text()函数外,还要用正则表达式匹配
        item['attr_n'] = movie.xpath('.//.../text()').re(r'...')
        yield item    #每提取一个item,就要记录一个,用yield item构造迭代器
                str(self.offset))
            yield Request(url=url,header=self.header)
复制代码

 

流程梳理

新建项目——>新建爬虫文件——>明确爬取的内容,写Item——>写爬虫程序——>交给写pipeline数据处理程序——>配置setting——>执行爬虫(①cmd;②程序中写run程序)

 

7、遇到的问题及解决

①、输出为CSV文件时只输出了最后一项

原因:step3中的yield item放在了for循环以外,导致只输出了一次

解决方法:step3中的yield item放在for循环最后

②、能通过response.xpath找到大的要素,但是通过for循环movie.xpath找具体小要素时却找不到

原因:movie.xpath的XPath路径表达式写的过程中,并没有加前缀.,因此就导致了找小要素时,并不是从之前已经找好的路径开始的,而是又从头开始了

解决方法:step3中写movie.xpath中的XPath表达式时,在路径最前加 .,表示从当前目录开始(具体见XPath - ShineLe - 博客园

③、大要素的xpath写好了,但是却并没有找到大要素

原因:部分大要素的属性中有空格,这些空格绝对不能省略

 

8、补充

①yield

yield是生成器(Generator)的标志,关于生成器的内容,可以看列表生成式 生成器 迭代器 yield - ShineLe - 博客园,简单来说,yield类似于return,区别在于yield会保留现场,当下次执行循环时,不会从头开始,而是从上次结束(即yield处)的地方开始。

Scrapy爬虫框架中关于这一部分的理解我觉得非常好,现引用至此——在本程序中,第一个yield,我们对item封装数据后,就调用yield将控制权移交给pipeline,pipeline拿到item处理后继续返回该程序。

第二个yield,程序中用到了回调(callback)机制,回调的对象是parse,即当前方法,通过不断回调,程序可以不间断地处理URL中包含的下一个URL。但要设置终止条件,不然可能会陷入死循环。

②XPath内容提取——extract

还有一个要注意的是,我们如何提取XPath中的数据,写法有多种:

item['name']=i.xpath('./a/@title')[0]#①拿到原数据,其中可能有我们用不到的东西
items['name']=i.xpath('./a/@title').extract()#②将原数据转化为字符串
items['name']=i.xpath('./a/@title').extract_first()#③拿到字符串中第一个数据,即我们要的数据;
items['name']=i.xpath('./a/@title').get()#
items['name']=i.xpath('./a/@title').extract()[0]#
#③④⑤作用相同,源代码中我们用的是⑤,其实都可以

 

posted @   ShineLe  阅读(604)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示