ActiveMQ 面试 linux springcloud 私有变量 ios uicollectionview Material UI vue中文 angular视频教程 jquery点击事件 jq选择第一个子元素 安卓小程序源码 excel动态图表制作 安卓程序源代码 mysql组合索引 solidworks图库 pythonlist python3文件操作 java的继承 java入门编程 java数据类型 java获取当前月份 java对象和类 java的集合框架 java正则替换 java匿名对象 java泛型方法 java命令 java配置jdk mac地址修改器 xp画图工具 找茬辅助 霜之祝福 数据库系统概论第五版 win10计算器下载 微信摇骰子表情包 快点蛆虫成就单刷 ezcad2 labview宝典
当前位置: 首页 > 学习教程  > python

Scrapy框架爬虫实战——从入门到放弃01

2021/2/7 9:55:04 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

Scrapy框架爬虫实战01——经常被爬的古诗文网 ps. 案例制作时的操作环境是MacOS,如果是windows用户,下文中提到的“终端”指的就是cmd命令行窗口。 pps. 本文省略了安装过程,尚未安装scrapy的用户可以直接在pycharm的preference内搜索安装。…

Scrapy框架爬虫实战01——经常被爬的古诗文网

ps. 案例制作时的操作环境是MacOS,如果是windows用户,下文中提到的“终端”指的就是cmd命令行窗口。

pps. 本文省略了安装过程,尚未安装scrapy的用户可以直接在pycharm的preference内搜索安装。

文章目录

  • Scrapy框架爬虫实战01——经常被爬的古诗文网
    • 项目创建
      • 各个文件的作用
    • 爬取第一页的内容并保存
      • 设置`settings.py`
      • 主要工作——编写`gsw_spider.py`
        • 运行的方法
        • 使用xpath提取数据
      • 配置`items.py`
      • 在`gsw_spider.py`里导入`items`
      • 进入`pipelines.py`和`settings.py`
    • 爬取后续内容
    • 针对反爬虫机制的修改完善
    • 最终的古诗文网Scrapy爬虫代码
      • `gsw_spider.py`
      • `items.py`
      • `pipelines.py`
      • `settings.py`

目标网站:传送门

任务:使用Scrapy框架爬虫,爬取“推荐”中共10页的古诗题目、作者、朝代和内容

ps. 各类教程都拿它举例子,古诗文网好惨

项目创建

创建Scrapy爬虫项目需要在终端中进行

先打开一个文件路径,即你希望的爬虫文件存放路径,比如我放在创建好的spidertest 文件夹中:

在这里插入图片描述

cd /Users/pangyuxuan/spidertest # 这是文件夹路径

使用命令创建项目:

scrapy startproject [项目名称]

创建爬虫:

cd [项目名称] # 先进入项目路径
scrapy genspider [爬虫名称] [目标域名] # 再创建爬虫文件

至此你已经创建好了scrapy爬虫文件,它应该长这样:

在这里插入图片描述

其中[项目名称]gsw_test[爬虫名称]gsw_spider

综上,创建一个基本的scrapy爬虫文件,一共在终端的命令行中输入了4行代码:

cd /Users/pangyuxuan/spidertest # 打开一个文件路径,作为爬虫的存放路径
scrapy startproject gsw_test # 创建scrapy项目,名为gsw_test
cd gsw_test # 打开项目路径
scrapy genspider gsw_spider https://www.gushiwen.org 
# 创建scrapy爬虫,爬虫名为gsw_spider,目标域名为 https://www.gushiwen.org

各个文件的作用

后续的编写还是依赖pycharm,所以在pycharm中打开项目文件:

在这里插入图片描述

其中各个文件的作用如下:

  1. settings.py:用来配置爬虫的,比如设置User-Agent、下载延时、ip代理。
  2. middlewares.py:用来定义中间件。
  3. items.py:用来提前定义好需要下载的数据字段
  4. pipelines.py:用来保存数据
  5. scrapy.cfg:用来配置项目

爬取第一页的内容并保存

以下内容请按顺序阅读并实现

设置settings.py

先在settings.py中做两项工作:

  1. 设置robots.txt协议为“不遵守”

    robots.txt是一个互联网爬虫许可协议,默认是True(遵守协议),如果遵守的话大部分网站都无法进行爬取,所以先把这个协议的状态设为不遵守

    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    

    ps. 所以这个协议的意义是什么。。。

  2. 配置请求头(设置user-agent

    # Override the default request headers:
    DEFAULT_REQUEST_HEADERS = {
       'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
       'Accept-Language': 'en',
       'user-agent' : '我自己的ua'
    }
    

主要工作——编写gsw_spider.py

import scrapy

class GswSpiderSpider(scrapy.Spider): # 我们的代码都写在这个类里面
    name = 'gsw_spider' # 爬虫的名字
    allowed_domains = ['https://www.gushiwen.org'] # 目标域名
    start_urls = ['http://https://www.gushiwen.org/'] # 爬虫的起始网页

    def parse(self, response):
        

目前的爬虫起始网页start_urls是自动生成的,我们把它换成古诗文网的第一页

start_urls = ['https://www.gushiwen.cn/default_1.aspx']

为了使打印出来的结果更加直观,我们编写myprint函数如下:

def myprint(self,value):
	print("="*30) # 在输出内容的上、下加一些'=',找起来方便
	print(value)
	print("="*30)

然后我们尝试打印一下当前爬取到的内容,应该为古诗文网第一页的信息。

目前为止,gsw_spider.py被改成了这样:

import scrapy

class GswSpiderSpider(scrapy.Spider):
    name = 'gsw_spider' # 爬虫的名字
    allowed_domains = ['https://www.gushiwen.org'] # 目标域名
    start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 起始页面

    def myprint(self,value):
        print("="*30)
        print(value)
        print("="*30)

    def parse(self, response):
        self.myprint(response.text) # 打印网页源代码



运行的方法

scrapy爬虫需要在终端里输入命令来运行,输入命令如下:

scrapy crawl gsw_spider # gsw_spider是爬虫名

方便起见,我们在项目目录里新建一个start.py,通过cmdline库里的函数来向终端发送命令,这样就不用不停地切换窗口了,而且运行结果可以在pycharm里直接展现,这样就与我们之前学的爬虫一样了。

后续我们无论修改哪个代码,都是运行start.py这个文件。

from scrapy import cmdline
cmds = ['scrapy','crawl','gsw_spider'] # 拼接命令语句
cmdline.execute(cmds) # 执行

点击运行,可以在运行窗口中看到结果:

在这里插入图片描述

截至目前为止,我们已经获取了网页源代码,接下来的工作就是从源代码中解析想要的数据了。

无需导入新的库,Scrapy框架为我们内置了许多函数,使我们仍可以用之前学习的数据解析知识(xpath、bs4和正则表达式)来完成数据提取。


使用xpath提取数据

使用xpath语法提取数据,返回的结果是选择器列表类型SelectorList,选择器列表里包含很多选择器Selector,即:

  • response.xpath返回的是SelectorList对象
  • SelectorList存储的是Selector对象

我们获取一下所有包含古诗标题的标签,输出返回值类型,以验证上面的结论:

def parse(self, response):
	gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
	self.myprint(type(gsw_divs)) # 打印获取到的div标签集的类型
	for gsw_div in gsw_divs :
	self.myprint(type(gsw_div)) # 打印标签集中的每个元素的类型

运行结果:

在这里插入图片描述

使用get()getall()函数从选择器类型的数据中提取需要的数据

  • get()返回选择器的第一个值(字符串类型)

  • getall()返回选择器的所有值(列表类型)

for gsw_div in gsw_divs :
	title_get = gsw_div.xpath(".//b/text()").get()
	title_getall = gsw_div.xpath(".//b/text()").getall()
	self.myprint(title_get) # 打印get函数的结果
	self.myprint(title_getall) # 打印getall函数的结果

输出:

在这里插入图片描述

我们共提取标题、朝代、作者、内容四部分信息,gsw_spider.py代码如下:

import scrapy

class GswSpiderSpider(scrapy.Spider):
    name = 'gsw_spider' # 爬虫的名字
    allowed_domains = ['https://www.gushiwen.org'] # 目标域名
    start_urls = ['https://www.gushiwen.cn/default_1.aspx'] # 起始页面

    def myprint(self,value): # 用于打印的函数
        print("="*30)
        print(value)
        print("="*30)

    def parse(self, response):
        gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
        for gsw_div in gsw_divs :
            title = gsw_div.xpath(".//b/text()").get() # 题目
            source = gsw_div.xpath(".//p[@class='source']/a/text()").getall() # 朝代+作者
            # source是getall函数的返回值,是个列表,故可以直接用下标调用
            dynasty = source[0] # 朝代
            writer = source[1] # 作者
            self.myprint(source)
            content = gsw_div.xpath(".//div[@class='contson']//text()").getall() # 诗文内容
            # 用//text()获取标签下的所有文本
            content = ''.join(content).strip() # 将列表拼接,并用strip()删除前后的换行/空格
            

你可以在任意地方插入self.myprint(内容)来进行打印,以验证数据是否被成功提取

接下来就是保存数据,我们先在items.py中配置好要保存的数据有哪些。


配置items.py

还记得这个文件是干什么用的吗?

items.py:用来提前定义好需要下载的数据字段

一共有上述四部分内容需要保存,因此我们的items.py应该这样写:

import scrapy

class GswTestItem(scrapy.Item):
    title = scrapy.Field() # 标题
    dynasty = scrapy.Field() # 朝代
    writer = scrapy.Field() # 作者
    content = scrapy.Field() # 内容

其中Field()可以理解为一种普适的变量类型,不管是字符串还是列表,都用scrapy.Field()来接收。


gsw_spider.py里导入items

定义完items.py后,我们在gsw_spiders.py导入它。需要注意的是,gsw_spiders.pyspiders文件夹里,也就是说items.pygsw_spiders.py上层目录中:

在这里插入图片描述

因此导入时,应该这样写:

from ..items import GswTestItem # ..表示上层目录

导入后,我们将对应参数传入,然后使用yield关键字进行返回

item = GswTestItem(title=title,dynasty=dynasty,writer=writer,content=content)
yield item

进入pipelines.pysettings.py

先在settings.py里把pipelines.py打开:

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'gsw_test.pipelines.GswTestPipeline': 300, 
  	# 300是这个pipeline的优先级,代表了执行顺序,数值越小优先级越大
}

再编写pipelines.py

from itemadapter import ItemAdapter

import json # 记得自己导入json库

class GswTestPipeline:
    def open_spider(self,spider):
        self.fp = open("古诗文.txt",'w',encoding='utf-8') # 制定文件名和编码格式

    def process_item(self, item, spider):
        self.fp.write(json.dumps(dict(item),ensure_ascii=False)+'\n') 
        # dict函数将item转化为字典
        # json.dumps()将字典格式的item转换为json字段
        # 参数ensure_ascii=False,用于存储中文
        # +'\n'用于将保存的内容自动换行
        return item

    def close_spider(self,spider): # 关闭文件
        self.fp.close()

上面的open_spider函数和close_spider函数虽然不是自带的,但它是一种模版化的函数(套路),是一种Scrapy框架提供的高效的文件存储形式。

我们自己写的时候,只要按上述样式编(默)写即可,根据自己的需求修改存储文件的文件名、格式和编码方式,但不能改变两个函数名!

现在我们运行start.py,就会发现路径下多了一个古诗文.txt,打开以后是这样:

在这里插入图片描述

至此,第一页爬取成功!(不要在意为什么只爬了一点就结束了,先往下看,最后会有修正)


爬取后续内容

爬取了第一页的内容以后,我们还需要继续往后寻找,先来找一下第二页的url:

右键检查“下一页”按钮以获取下一页的url

在这里插入图片描述

为了测试寻找下一页的功能,我们暂时忽略之前的代码

def parse(self, response):
	next_href = response.xpath("//a[@id='amore']/@href").get() # 获取href属性
	next_url = response.urljoin(next_href) # 给/default_2.aspx添加前缀域名使其变完整
	self.myprint(next_url) # 输出以验证

在这里插入图片描述

找到了!

接下来我们就用一个request来接收scrapy.Request(next_url)的返回值,并使用yield关键字来返回即可:

next_href = response.xpath("//a[@id='amore']/@href").get()
next_url = response.urljoin(next_href)
request = scrapy.Request(next_url)
yield request

需要注意的是,我们需要给“寻找下一页”操作设立一个终止条件,当下一页不存在的时候停止访问,所以最后的代码长这个样子:

# 获取下一页
next_href = response.xpath("//a[@id='amore']/@href").get()
if next_href:
	next_url = response.urljoin(next_href)
	request = scrapy.Request(next_url)
	yield request

针对反爬虫机制的修改完善

此时我们的代码是这样的:

gsw_spider.py

import scrapy
from ..items import GswTestItem

class GswSpiderSpider(scrapy.Spider):
    name = 'gsw_spider' # 爬虫的名字
    allowed_domains = ['https://www.gushiwen.org'] # 目标域名
    start_urls = ['https://www.gushiwen.cn/default_1.aspx']

    def myprint(self,value):
        print("="*30)
        print(value)
        print("="*30)

    def parse(self, response):
        gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
        for gsw_div in gsw_divs :
            title = gsw_div.xpath(".//b/text()").get() # 古诗题目
            source = gsw_div.xpath(".//p[@class='source']/a/text()").getall() # 朝代+作者
            # source是getall函数的返回值,是个列表,直接用下标调用
            dynasty = source[0] # 朝代
            writer = source[1] # 作者
            content = gsw_div.xpath(".//div[@class='contson']//text()").getall() # 诗文内容
            # 用//text()获取标签下的所有文本
            content = ''.join(content).strip() # 将列表拼接,并用strip()删除前后的换行/空格
            item = GswTestItem(title=title,dynasty=dynasty,writer=writer,content=content)
            yield item
        # 获取下一页
        next_href = response.xpath("//a[@id='amore']/@href").get()
        if next_href:
            next_url = response.urljoin(next_href)
            request = scrapy.Request(next_url)
            yield request

运行后,会报这样一个错误:IndexError: list index out of range,意思是“列表的下标索引超过最大区间”。

为什么会有这样的错误呢?

我们可以在网页上看到,页面上不全是古诗文:

在这里插入图片描述

除了古诗文外,这种短句子也是在class=sons的标签下,按照我们的查找方式:

gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
for gsw_div in gsw_divs :
  	source = gsw_div.xpath(".//p[@class='source']/a/text()").getall()

找到图中蓝色的div标签以后,它里面是没有p标签的,也就是说此时的source是个空表,直接调用source[0]那必然是要报错的。

这算是网站的一种反爬虫机制,利用格式不完全相同的网页结构来让你的爬虫报错,太狠了!!

为了解决这个问题,我们添加try...except结构如下:

for gsw_div in gsw_divs :
  	title = gsw_div.xpath(".//b/text()").get()
		source = gsw_div.xpath(".//p[@class='source']/a/text()").getall()
		try:
				dynasty = source[0]
				writer = source[1]
				content = gsw_div.xpath(".//div[@class='contson']//text()").getall()
				content = ''.join(content).strip()
				item = GswTestItem(title=title,dynasty=dynasty,writer=writer,content=content)
				yield item
    except:
        print(title) # 打印出错的标题以备检查

这样,上面的报错就被完美解决了。

然鹅,一波未平一波又起,bug永远是生生不息源源不绝的

我们发现了一个新的报错:DEBUG: Filtered offsite request to 'www.gushiwen.cn': <GET https://www.gushiwen.cn/default_2.aspx>

这是因为我们在最开始的allowed_domains里限制了访问的域名:“https://www.gushiwen.org”

在这里插入图片描述

而到了第二页的时候,网站偷偷把域名换成.cn了!

.cn不是.org,我们的爬虫没法继续访问,所以就停了。这又是这个网站的一个反爬虫机制,我们只需要在allowed_domains里添加一个.cn的域名,这个问题就可以得到妥善的解决:

allowed_domains = ['gushiwen.org','gushiwen.cn']

运行可得到期望结果:

在这里插入图片描述

最终的古诗文网Scrapy爬虫代码

gsw_spider.py

import scrapy
from ..items import GswTestItem

class GswSpiderSpider(scrapy.Spider):
    name = 'gsw_spider' # 爬虫的名字
    # allowed_domains = ['https://www.gushiwen.org'] # 目标域名
    allowed_domains = ['gushiwen.org','gushiwen.cn']
    start_urls = ['https://www.gushiwen.cn/default_1.aspx']

    def myprint(self,value):
        print("="*30)
        print(value)
        print("="*30)

    def parse(self, response):
        gsw_divs = response.xpath("//div[@class='left']/div[@class='sons']")
        for gsw_div in gsw_divs :
            title = gsw_div.xpath(".//b/text()").get() # 古诗题目
            source = gsw_div.xpath(".//p[@class='source']/a/text()").getall() # 朝代+作者
            # source是getall函数的返回值,是个列表,直接用下标调用
            try:
                dynasty = source[0] # 朝代
                writer = source[1] # 作者
                content = gsw_div.xpath(".//div[@class='contson']//text()").getall() # 诗文内容
                # 用//text()获取标签下的所有文本
                content = ''.join(content).strip() # 将列表拼接,并用strip()删除前后的换行/空格
                item = GswTestItem(title=title,dynasty=dynasty,writer=writer,content=content)
                yield item
            except:
                print(title)
        # 获取下一页
        next_href = response.xpath("//a[@id='amore']/@href").get()
        if next_href:
            next_url = response.urljoin(next_href)
            request = scrapy.Request(next_url)
            yield request

items.py

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class GswTestItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    dynasty = scrapy.Field()
    writer = scrapy.Field()
    content = scrapy.Field()

pipelines.py

from itemadapter import ItemAdapter
import json

class GswTestPipeline:
    def open_spider(self,spider):
        self.fp = open("古诗文.txt",'w',encoding='utf-8')

    def process_item(self, item, spider):
        self.fp.write(json.dumps(dict(item),ensure_ascii=False)+'\n') # dict函数将item转化为字典,再转换为json字段进行保存
        return item

    def close_spider(self,spider):
        self.fp.close()

settings.py

为了看起来简洁一点,注释部分我就都删了

BOT_NAME = 'gsw_test'

SPIDER_MODULES = ['gsw_test.spiders']
NEWSPIDER_MODULE = 'gsw_test.spiders'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
   'Accept-Language': 'en',
   'user-agent' : '我的user-agent'
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'gsw_test.pipelines.GswTestPipeline': 300,
}

大功告成!


本文链接: http://www.dtmao.cc/news_show_2000192.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?