ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

使用Scrapy框架进行爬虫并存储到数据库

2021-07-19 14:31:21  阅读:362  来源: 互联网

标签:菜谱 数据库 爬虫 response Scrapy menuitem div extract class


使用Scrapy框架爬取美食杰的菜谱信息

1.前提环境

安装好Pycharm,Pycharm里安装好Scrapy框架

2.创建Scrapy工程

在Pycharm左下角栏目找到Terminal,如下图:
在这里插入图片描述
点击Terminal,进入命令行窗口,如下图:
在这里插入图片描述
在该窗口执行以下命令,创建Scrapy工程

 scrapy startproject 项目名称
   例:scrapy startproject menu

进入工程目录,找到spiders文件夹,在其目录下用下面命令创建爬虫器。

scrapy genspider 爬虫名 域名
   例: scrapy genspider getmenu https://www.meishij.net/

这时候我们的工程目录是这样的
在这里插入图片描述
为了方便运行爬虫程序,我创建了一个start.py文件来启动爬虫。
start.py

from scrapy import cmdline
cmdline.execute('scrapy crawl getmenu'.split())  #getmenu应该换成自己创建的爬虫器名称,我上面创建的爬虫器名称是getmenu

3.修改基本配置

3.1配置模拟请求

在配置文件settings.py中找到含有USER_AGENT这一行,把它的值改为浏览器的user-agent。=

USER_AGENT ='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'

user-agent的获取方式:打开浏览器,随便进入一个网页,按F12或右键-检查元素,出现下图窗口,并点击“网络”,窗口如下:
在这里插入图片描述然后刷新页面,一堆请求就展示出来了,如下图:
在这里插入图片描述随便点击一个请求,就会显示详细的请求信息,这时候就可以在详细的请求信息找到user-agent
在这里插入图片描述

3.2配置爬虫间隔

在配置文件settings.py中找到DOWNLOAD_DELAY,并设置你想要的时间间隔,单位是秒(s)

DOWNLOAD_DELAY = 0.1          #每隔0.1s爬取一条数据

当然,settings.py中还有很多参数可以设置,目前用暂时用不到,就先不作配置。

4.编写爬虫器的代码

在爬取网页数据时,我们往往要对网页的结构进行简单分析,只有这样才能写出高效率的爬虫程序。

4.1确定爬虫的目标网址

我的目标网址是:https://www.meishij.net/fenlei/chaofan/(美食杰的炒饭分区,如下图)
在这里插入图片描述

4.2确定要爬取的数据项

我想爬取的数据项有:菜谱名称、菜谱图片、做法步骤、主材料、辅料等菜谱的详细信息,而在上面的目标的网址中,只展示了菜谱的图片和名称等内容,不够详细,并不能达到我的要求。所以,就可能需要编写二级或多级url请求来实现进入菜谱的详情页面。
确定了要爬取的数据项后,为了方便对爬取的数据进行存储、修改等操作,编写items.py文件(当然,如果你只是想要观察一下爬取到了什么数据,可以不编写,在爬虫器每爬取一条数据就输出即可)。items.py如下:

import scrapy
class MenuItem(scrapy.Item):
    name = scrapy.Field()  #菜谱名称
    img = scrapy.Field()   #菜谱图片
    step = scrapy.Field()  #做法步骤
    material1 = scrapy.Field()  #主材料
    material2 = scrapy.Field()  #辅料
    energy = scrapy.Field()  #热量
    sugar = scrapy.Field()  #含糖量
    fat = scrapy.Field()   #脂肪
    needtime = scrapy.Field()  #所需时间
    uptime = scrapy.Field() # 上传时间(这条不需要通过爬虫获得,在生成每条爬虫记录时插入系统时间即可)
    level = scrapy.Field() #难度等级

4.3编写爬虫器

上面我们已经规划好要从什么网站爬取什么数据了,接下来就是编写爬虫器了。
首先,我们要确保从目标网址可以爬取到整个页面,以下代码验证以下是否可以爬取到https://www.meishij.net/fenlei/chaofan/的页面。

from datetime import datetime
import scrapy
from ..items import MenuItem
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/'] #爬取的目标网址
 	def parse(self, response):
 		print(response.text)  #打印爬取的页面
    

然后运行start.py,执行爬虫程序,结果如下:
在这里插入图片描述从图中打印结果看出,这个网址的内容是能够被爬取成功的。
接下来,根据items.py文件中你要爬取的数据项,看看这个网址中的数据能不能满足你的要求。
通过观察,在这个网址中(https://www.meishij.net/fenlei/chaofan/),只有菜谱名称、菜谱图片能够爬取到,而做法步骤、主材料、热量、含糖量等数据项根本不存于这个网页。于是我们随便点进去一个菜谱,查看以下它的详情页(https://www.meishij.net/zuofa/huangjindanchaofan_22.html为例子),如下:
在这里插入图片描述我惊人地发现我要爬取的数据项都在这个网页中(手动狗头)[/doge],于是,我只要通过目标网址,也就是爬虫开始的网址(https://www.meishij.net/fenlei/chaofan/)中跳转到对应的菜谱详情网址,然后进行爬取即可。
那么,如何在目标网址中跳转到对应的菜谱详情网址呢?我们需要对目标网址源码进行适当的分析

在目标页面按F12或者右键-检查元素打开下图窗口:
在这里插入图片描述将鼠标放在某个菜谱的位置右键-检查元素
在这里插入图片描述点击之后如下:
在这里插入图片描述由上图可知,在class=“imgw”的div下有一个class=“list_s2_item_img”的a标签,它的href属性就是菜谱的详情链接,所以,要想进入每个菜谱的详情也买你,就要通过这条链接进入。
这时,爬虫器代码如下:


import scrapy
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/'] 
    def parse(self, response):
        urls = response.xpath('//div[@class="imgw"]/a[@class="list_s2_item_img"]/@href').extract() #菜谱详情的链接
        #每爬取到一条链接,就提交给下一个函数爬取菜谱详情。
        for url in urls:
            yield scrapy.Request(url, callback=self.parse2, dont_filter=True)  # 提交url到下一层
    def parse2(self, response):
       print(response.text)

在上面代码中,parse函数的作用是从它开始进行爬虫,它只爬取网页的每个菜谱对应的链接,每爬取一条链接就提交给parse2函数。parse2函数是对单个菜谱的详情信息进行爬取。

通过上面的方法,我们可以通过目标网址(https://www.meishij.net/fenlei/chaofan/)来进入前页的所有菜谱(21条)的详情页面,下面将编写parse2函数,对菜谱的详情页进行爬取。

4.3.1爬取菜谱名称、难度、所需时间、主料、辅料

随便点进去一个菜谱详情页,并将鼠标移到菜谱名称出,然后右键-检查元素,如下图
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210719112842462.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xfU2hha2Vy,由上图可见,菜谱名称存放在class="recipe_title"的div下的h1标签中,且它class="recipe_title"的又被包含在class="recipe_header_info"的div里面,将鼠标移动到class="recipe_header_info"的div,发现这个div存放的是下图信息
在这里插入图片描述
由图可知,菜谱名称,难度、所需时间、主料、辅料都在这个div中,而这些都是我们需要爬取的数据,这时,爬虫器代码如下:

import scrapy
from ..items import MenuItem
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/']
    def parse(self, response):
        urls = response.xpath('//div[@class="imgw"]/a[@class="list_s2_item_img"]/@href').extract()
        for url in urls:
            yield scrapy.Request(url, callback=self.parse2, dont_filter=True)  # 提交url到下一层
    def parse2(self, response):
        menuitem = MenuItem()
        #菜谱名
        menuitem['name'] = response.xpath('//div[@class="recipe_header_c"]/div[2]/h1/text()').extract()
        #主材料+整合
        mat1 = ""
        material1list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[1]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material1list)):
            mat1 = mat1 + material1list[i]
        menuitem['material1'] = mat1
        #配料+整合
        mat2 = ""
        material2list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[2]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material2list)):
            mat2 = mat2 + material2list[i]
        menuitem['material2'] = mat2
        #难度等级
        menuitem['level'] = response.xpath('//div[@class="info2"]/div[4]/strong/text()').extract()
        #所需时间
        menuitem['needtime'] = response.xpath('//div[@class="info2"]/div[3]/strong/text()').extract()
        print(menuitem)

运行start.py启动爬虫程序,结果如下图
在这里插入图片描述由图可知,我们获取到了菜谱名称,难度、所需时间、主料、辅料,但是除了主料、辅料这两个数据项外,其他数据项的值是一个列表,这对我们操作数据是非常不方便的(例如要把爬虫数据存入数MySQL数据库,列表类型的数据是存不进去的),所以我们将列表类型的数据转换成字符串类型,这时,爬虫器代码如下:

import scrapy
from ..items import MenuItem
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/']
    def parse(self, response):
        urls = response.xpath('//div[@class="imgw"]/a[@class="list_s2_item_img"]/@href').extract()
        for url in urls:
            yield scrapy.Request(url, callback=self.parse2, dont_filter=True)  # 提交url到下一层
    def parse2(self, response):
        menuitem = MenuItem()
        #菜谱名
        menuitem['name'] = response.xpath('//div[@class="recipe_header_c"]/div[2]/h1/text()').extract()
        #主材料+整合
        mat1 = ""
        material1list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[1]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material1list)):
            mat1 = mat1 + material1list[i]
        menuitem['material1'] = mat1
        #配料+整合
        mat2 = ""
        material2list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[2]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material2list)):
            mat2 = mat2 + material2list[i]
        menuitem['material2'] = mat2
        #难度等级
        menuitem['level'] = response.xpath('//div[@class="info2"]/div[4]/strong/text()').extract()
        #所需时间
        menuitem['needtime'] = response.xpath('//div[@class="info2"]/div[3]/strong/text()').extract()
        #格式化--列表转字符串
        menuitem['name'] = ''.join(menuitem['name'])
        menuitem['level'] = ''.join(menuitem['level'])
        menuitem['needtime'] = ''.join(menuitem['needtime'])
        print(menuitem)

运行start.py启动爬虫程序,结果如下图
在这里插入图片描述目前为止,我们已经成功爬取了菜谱名称,难度、所需时间、主料、辅料,还需要爬取的数据有:

4.3.2爬取菜谱图片链接

与上面方法同理,不难发现,图片链接class="recipe_header_c"的div下的第一个div下的img标签的src属性。
在这里插入图片描述故爬虫器代码的parse2里面应该加上下面的代码

#菜谱图片
        menuitem['img'] = response.xpath('//div[@class="recipe_header_c"]/div[1]/img/@src').extract()

4.3.3爬取菜谱的做法步骤

与以上方法同理,不难发现,class="recipe_step"的div有许多个,经过验证,是存放步骤的div,且每个div存放一个步骤,这时我们需要将所以步骤拿到,即拿到所有存放步骤的div,然后取出其里面的class="step_content"的div的p标签的内容,就是每个步骤的内容啦
在这里插入图片描述所以,为了爬取菜谱的做法步骤,爬虫器代码的parse2应增加以下代码:

#步骤+整合
        steplist = response.xpath('//div[@class="recipe_step"]/div[@class="step_content"]/p/text()').extract()  #这里存放了一道菜谱的多个步骤
        step = ""
        for i in range(0, len(steplist)):   #遍历步骤列表,合成一个字符串作为菜谱步骤
            step = step + steplist[i]
        menuitem['step'] = step

4.3.4爬取热量、含糖量、脂肪含量

与上面方法同理,不难发现,热量、含糖量、脂肪含量都被包含在class="jztbox“的div,如下图
在这里插入图片描述经过观察,热量在class="jztbox“的div下面的第3个div下面的第1个div下的内容,含糖量在class="jztbox“的div下面的第2个div下面的第1个div下的内容,脂肪含量在class="jztbox“的div下面的第4个div下面的第1个div下的内容,故爬虫器代码的parse2应增加以下代码:

menuitem['energy'] = response.xpath('//div[@class="jztbox"]/div[3]/div[1]/text()').extract()
        menuitem['sugar'] = response.xpath('//div[@class="jztbox"]/div[2]/div[1]/text()').extract()
        menuitem['fat'] = response.xpath('div[@class="jztbox"]/div[4]/div[1]/text()').extract()

这时,爬虫器的代码如下:

import scrapy
from ..items import MenuItem
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/']
    def parse(self, response):
        urls = response.xpath('//div[@class="imgw"]/a[@class="list_s2_item_img"]/@href').extract()
        for url in urls:
            yield scrapy.Request(url, callback=self.parse2, dont_filter=True)  # 提交url到下一层
    def parse2(self, response):
        menuitem = MenuItem()
        #菜谱名
        menuitem['name'] = response.xpath('//div[@class="recipe_header_c"]/div[2]/h1/text()').extract()
        #主材料+整合
        mat1 = ""
        material1list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[1]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material1list)):
            mat1 = mat1 + material1list[i]
        menuitem['material1'] = mat1
        #配料+整合
        mat2 = ""
        material2list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[2]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material2list)):
            mat2 = mat2 + material2list[i]
        menuitem['material2'] = mat2
        #难度等级
        menuitem['level'] = response.xpath('//div[@class="info2"]/div[4]/strong/text()').extract()
        #所需时间
        menuitem['needtime'] = response.xpath('//div[@class="info2"]/div[3]/strong/text()').extract()
        # 菜谱图片
        menuitem['img'] = response.xpath('//div[@class="recipe_header_c"]/div[1]/img/@src').extract()
        # 步骤+整合
        steplist = response.xpath('//div[@class="recipe_step"]/div[@class="step_content"]/p/text()').extract()
        step = ""
        for i in range(0, len(steplist)):
            step = step + steplist[i]
        menuitem['step'] = step
        #热量、含糖量、脂肪含量
        menuitem['energy'] = response.xpath('//div[@class="jztbox"]/div[3]/div[1]/text()').extract()
        menuitem['sugar'] = response.xpath('//div[@class="jztbox"]/div[2]/div[1]/text()').extract()
        menuitem['fat'] = response.xpath('div[@class="jztbox"]/div[4]/div[1]/text()').extract()
        #格式化--列表转字符串
        menuitem['name'] = ''.join(menuitem['name'])
        menuitem['level'] = ''.join(menuitem['level'])
        menuitem['needtime'] = ''.join(menuitem['needtime'])
        menuitem['img'] = ''.join(menuitem['img'])
        menuitem['energy'] = ''.join(menuitem['energy'])
        menuitem['sugar'] = ''.join(menuitem['sugar'])
        menuitem['fat'] = ''.join(menuitem['fat'])
        print(menuitem)

5.将爬虫数据存储到数据库(MySQL)

5.1安装MySQLdb插件

MySQLdb用于在python中连接数据库,将这个文件(mysqlclient-2.0.1-cp37-cp37m-win_amd64.whl 提取码:6666)拷贝到Pycharm的工作空间下(我的是workplace,是自定义的),然后在Terminal命令窗口进入该文件所在的文件夹,执行 install mysqlclient-2.0.1-cp37-cp37m-win_amd64.whl,等待安装完成即可。
验证是否安装成功,只需要在.py文件

import MySQLdb

不报错即可

5.2数据库前期准备

新建一个名称位menu的数据库
创建一个用户名为user2、密码为123的用户
创建一个cookbook2的表
cookbook2结构如下(注意编码格式,设置能展示中文的,否则可能插入会出现乱码或者插入失败)
在这里插入图片描述

5.3数据库连接配置

在settings.py文件增加以下代码:

mysql_host='127.0.0.1'        	#地址
mysql_user='user2'              #经过授权的用户名
mysql_passwd='123'              #经过授权的访问密码
mysql_db='menu'                 #数据库名称
mysql_tb='cookbook2'             #表名
mysql_port=3306             #端口,默认3306

在settings.py文件找到ITEM_PIPELINES这个参数,并增加以下代码:

'menu.pipelines.MenusqlPipeline': 2,

我的ITEM_PIPELINES如下:

ITEM_PIPELINES = {
			#数字越小优先级越高
			#menu.pipelines.Class,Class与pipelines.py里的类名对应,如:menu.pipelines.MenusqlPipeline
    'menu.pipelines.MenuPipeline': 300,   
    'menu.pipelines.MenusqlPipeline': 2,        #提交到数据库
}

在pipelines.py增加以下代码:

import MySQLdb
from .settings import mysql_host,mysql_db,mysql_user,mysql_passwd,mysql_port
class MenusqlPipeline(object):
    def __init__(self):
        host=mysql_host
        user=mysql_user
        passwd=mysql_passwd
        port=mysql_port
        db=mysql_db
        self.connection=MySQLdb.connect(host,user,passwd,db,port,charset='utf8')
        self.cursor=self.connection.cursor()
    def process_item(self, item, spider):
        sql = "insert into cookbook2(name,step,sugar,energy,fat,material1,needtime,img,level,material2) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
         #注意 params添加的顺序要和sql语句插入的顺序一致,否则会出现错位甚至插入失败的情况
        params=list()
        params.append(item['name'])
        params.append(item['step'])
        params.append(item['sugar'])
        params.append(item['energy'])
        params.append(item['fat'])
        params.append(item['material1'])
        params.append(item['needtime'])
        params.append(item['material2'])
        params.append(item['img'])
        params.append(item['level'])
        self.cursor.execute(sql,tuple(params))
        self.connection.commit()
        return item
    def __del__(self):
        self.cursor.close()
        self.connection.close()

5.4启动爬虫

爬虫器最后一行增加 return menuitem,如下:

import scrapy
from ..items import MenuItem
class GetmenuSpider(scrapy.Spider):
    name = 'getmenu'
    allowed_domains = ['https://www.meishij.net']
    start_urls = ['https://www.meishij.net/fenlei/chaofan/']
    def parse(self, response):
        urls = response.xpath('//div[@class="imgw"]/a[@class="list_s2_item_img"]/@href').extract()
        for url in urls:
            yield scrapy.Request(url, callback=self.parse2, dont_filter=True)  # 提交url到下一层
    def parse2(self, response):
        menuitem = MenuItem()
        #菜谱名
        menuitem['name'] = response.xpath('//div[@class="recipe_header_c"]/div[2]/h1/text()').extract()
        #主材料+整合
        mat1 = ""
        material1list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[1]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material1list)):
            mat1 = mat1 + material1list[i]
        menuitem['material1'] = mat1
        #配料+整合
        mat2 = ""
        material2list = response.xpath(
            '//div[@class="recipe_ingredientsw"]/div[2]/div[2]/strong/a/text()').extract()
        for i in range(0, len(material2list)):
            mat2 = mat2 + material2list[i]
        menuitem['material2'] = mat2
        #难度等级
        menuitem['level'] = response.xpath('//div[@class="info2"]/div[4]/strong/text()').extract()
        #所需时间
        menuitem['needtime'] = response.xpath('//div[@class="info2"]/div[3]/strong/text()').extract()
        # 菜谱图片
        menuitem['img'] = response.xpath('//div[@class="recipe_header_c"]/div[1]/img/@src').extract()
        # 步骤+整合
        steplist = response.xpath('//div[@class="recipe_step"]/div[@class="step_content"]/p/text()').extract()
        step = ""
        for i in range(0, len(steplist)):
            step = step + steplist[i]
        menuitem['step'] = step
        #热量、含糖量、脂肪含量
        menuitem['energy'] = response.xpath('//div[@class="jztbox"]/div[3]/div[1]/text()').extract()
        menuitem['sugar'] = response.xpath('//div[@class="jztbox"]/div[2]/div[1]/text()').extract()
        menuitem['fat'] = response.xpath('div[@class="jztbox"]/div[4]/div[1]/text()').extract()
        #格式化--列表转字符串
        menuitem['name'] = ''.join(menuitem['name'])
        menuitem['level'] = ''.join(menuitem['level'])
        menuitem['needtime'] = ''.join(menuitem['needtime'])
        menuitem['img'] = ''.join(menuitem['img'])
        menuitem['energy'] = ''.join(menuitem['energy'])
        menuitem['sugar'] = ''.join(menuitem['sugar'])
        menuitem['fat'] = ''.join(menuitem['fat'])
        # print(menuitem)
        return menuitem

通过运行start.py启动爬虫,查看数据库是否成功插入:在这里插入图片描述插入成功!

标签:菜谱,数据库,爬虫,response,Scrapy,menuitem,div,extract,class
来源: https://blog.csdn.net/L_Shaker/article/details/118891300

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有