嘎里三分熟
  • 首页
  • JMusic
  • TSBay
  • 常用工具
  • About Me
  • 留言板
一行代码一世浮生
  1. 首页
  2. Python
  3. 正文

Scrapy框架入门

2018年12月10日 2152点热度 1人点赞 0条评论

一、环境

  • windows

  • python3.6.4

  • scrapy1.5.1

  • 明白Python的基本语法系列


二、知识点

  • xpath

  • 文字内容爬取并存本地文件

  • 翻页爬取

  • 图片爬取并存本地

  • 简单的反爬虫

  • 数据存数据库(mysql)

  • 日志

  • 网站地址:https://movie.douban.com/top250


三、项目构建及文件说明

    1、项目创建

scrapy startproject Douban

    2、项目初始化

cd Douban
scrapy genspider douban "douban.com"

    3、各文件说明

        scrapy01.png

四、xpath解析说明

    以Chrome插件(XPath Helper)为例。
//ol[@class='grid_view']/li/div[@class='item']
即可解析出所有的电影信息模块,然后循环遍历进行处理即可。
    PS:“//”即代表从任意路径下开始寻找
    

五、字段设置

    即 item.py 文件。
# 电影名字
film_name = scrapy.Field()
# 导演和主演名字
director_performer_name = scrapy.Field()
# 主演名字
# performer_name = scrapy.Field()
# 电影上映年份
film_year = scrapy.Field()
# 电影国家
film_country = scrapy.Field()
# 电影类型
film_type = scrapy.Field()
# 电影评分
film_rating = scrapy.Field()
# 电影评论人数
film_reviews_num = scrapy.Field()
# 电影经典语句
film_quato = scrapy.Field()
# 电影图片
film_img_url = scrapy.Field()

六、爬虫编写

    即 douban.py 文件。
    以下代码,注释很详细,细节暂不赘述,简单提一下 yield 的用法:
    yield 是个很重要的语法,有着 return 的部分功能,但完全不同于 return。
    return 会返回信息并且终止当前的方法,而 yield 虽然也会返回一个信息给调用者,但是调用者使用完了之后程序还会回到此处继续执行。
    比如用在此爬虫的 for 循环中的妙处是:此处生成 item 之后返回给调度器进行相关的处理,然后程序再回到这里继续运行,即继续下一个循环,然后再生成一个新的 item 提供给调度器,如此往复,直到循环结束。
# 爬虫名称(必须唯一)
name = 'douban'
# 非此域名下的链接均不进行爬取
allowed_domains = ['douban.com']
base_url = 'https://movie.douban.com/top250'
off_set = '?start=0&filter='
# 起始的爬取地址
start_urls = [base_url + off_set]
# 每次的爬取都会默认走这个 parse 方法
def parse(self, response):
    # xpath 解析出每个电影的信息模块
    films = response.xpath("//ol[@class='grid_view']/li/div[@class='item']")
    # 遍历每个电影模块
    for film in films:
        # 创建电影信息存储的item对象
        item = DoubanItem()
        # 标题
        titles = film.xpath("./div[@class='info']/div[@class='hd']/a/span/text()").extract()
        film_name = ''
        # 拼接电影名
        for title in titles:
            film_name += title.strip()
        # 电影信息
        infos = film.xpath("./div[@class='info']/div[@class='bd']/p/text()").extract()
        director_performer_name = ""
        for temp in infos[0]:
            director_performer_name += temp.strip()
        year_country = infos[1]
        film_year = year_country.split("/")[0].strip()
        film_country = year_country.split("/")[1].strip()
        film_type = year_country.split("/")[2].strip()
        # 电影评分
        film_rating = film.xpath("./div[@class='info']/div[@class='bd']/div[@class='star']/span[@class='rating_num']/text()").extract()[0].strip()
        # 电影参与评论人数
        film_reviews_num = film.xpath("./div[@class='info']/div[@class='bd']/div[@class='star']/span[last()]/text()").extract()[0].strip()[:-3]
        # 电影经典语句
        film_quato = ""
        film_quato_temp = film.xpath("./div[@class='info']/div[@class='bd']/p[@class='quote']/span/text()").extract()
        if film_quato_temp:
            film_quato = film_quato_temp[0].strip()
        # 电影图片链接
        film_img_url = film.xpath("./div[@class='pic']/a/img/@src").extract()[0].strip()
        # item 字段赋值
        item['film_name'] = film_name
        item['director_performer_name'] = director_performer_name
        # item['director_name'] = director_name
        # item['performer_name'] = performer_name
        item['film_year'] = film_year
        item['film_country'] = film_country
        item['film_type'] = film_type
        item['film_rating'] = film_rating
        item['film_reviews_num'] = film_reviews_num
        item['film_quato'] = film_quato
        item['film_img_url'] = film_img_url
        # 返回 item 进行解析,解析完了之后再回到这里继续运行
        yield item
    # 翻页
    # 解析出下一页
    next_url = response.xpath("//div[@class='paginator']/span[@class='next']/a/@href").extract()
    if next_url:
    # 如果下一页存在的话再进行请求,并传递回调函数 parse()
    yield scrapy.Request(self.base_url + next_url[0], self.parse)

七、“管道”说明

    即 pipelines.py 文件。
    说明:正如其名“管道”,它是用来处理 item 的,所以,我们可以写多个“管道”文件来处理 item,但是要注意:         ① 每个“管道”处理完之后记得 return item,否则后续管道无法再进行处理,毕竟拿不到了嘛;         ② 管道是有执行顺序的,所以需要我们进行定义其顺序(settings.py 文件),数字小,先执行:
    # Configure item pipelines    
    # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
       'Douban.pipelines.DoubanMoviePipeline': 300,
       'Douban.pipelines.DoubanImgPipeline': 301,
       'Douban.pipelines.DoubanDBPipeline': 400
    }

    1、文本内容存本地文件

        即配置里面的:'Douban.pipelines.DoubanMoviePipeline': 300,         也是比较简单,看下文代码的注释即可,但是要注意编码。
class DoubanMoviePipeline(object):
    """
    处理电影信息
    """
    
    def __init__(self):
        # 初始化:文件打开
        self.f = codecs.open("doubanData.json", mode="w", encoding="utf-8")
        
    def process_item(self, item, spider):
        # 内容,结尾增加了换行
        content = json.dumps(dict(item), ensure_ascii=False) + ",\n"
        # 内容写入文件
        self.f.write(content)
        # 一定要记得 return,否则之后的 pipeline 拿不到 item,也就没法继续处理了
        return item
        
    def close_spider(self, spider):
        # 爬虫关闭时进行:文件关闭
        self.f.close()

    2、图片内容保存本地

        我们写在同一个“管道”文件里面。
        注意继承类:ImagesPipeline,源码见:D:\IT\Python\Python36\Lib\site-packages\scrapy\pipelines\images.py         注意在 settings.py 中设置图片的下载路径:IMAGES_STORE = "D:\IT\Python\workspace\SpiderDemo\Douban\images\\"         代码同样比较简单,见下面的注释即可,注意,此处进行了文件重命名操作,并有打异常日志,日志后面会讲到。
class DoubanImgPipeline(ImagesPipeline):
    """
    处理图片信息
    """
    def get_media_requests(self, item, info):
        """
        图片下载
        """
        film_img_url = item['film_img_url']
        yield scrapy.Request(film_img_url)
        
    def item_completed(self, results, item, info):
        """
        图片重命名
        """
        # 获取文件下载的路径
        path = [x['path'] for ok, x in results if ok]
        # 原始的完整路径
        film_img_disk_url1 = settings.IMAGES_STORE + path[0]
        # 准备存放的新的完整路径
        film_img_disk_url = settings.IMAGES_STORE + 'full\\' + item['film_name'].split("/")[0].strip() + ".jpg"
        try:
            # 重命名
            os.rename(film_img_disk_url1, film_img_disk_url)
        except Exception as error:
            Logger(logLevel='error').getLogger().error("图片重命名失败,异常信息:%s" % error)
            pass
        return item

    3、数据存数据库

        还是写在同一个“管道”文件里面。

        “管道”配置为:'Douban.pipelines.DoubanDBPipeline': 400         settings.py 中配置数据库信息:
    # mysql 设置    
    MYSQL_HOST = 'localhost'
    MYSQL_PORT = 3380
    MYSQL_DBNAME = 'scrapy'
    MYSQL_USER = 'root'
    MYSQL_PASSWD = 'root'
        代码理解也不困难,见注释即可,此处进行了简单的查重处理。
class DoubanDBPipeline(object):
    """
    数据存入mysql
    """
    def __init__(self):
        # 连接数据库
        self.connect = pymysql.connect(
            host=settings.MYSQL_HOST,
            port=settings.MYSQL_PORT,
            db=settings.MYSQL_DBNAME,
            user=settings.MYSQL_USER,
            passwd=settings.MYSQL_PASSWD,
            charset='utf8mb4',
            use_unicode=True
        )
        self.cursor = self.connect.cursor()
        
    def process_item(self, item, spider):
        try:
            # 数据库查重
            self.cursor.execute(
                """select film_name from douban_movie_top_250 where film_name = %s and film_img_url = %s""",
                (item['film_name'], item['film_img_url'])
            )
            # 查重
            repetition = self.cursor.fetchone()
            if repetition:
                # 数据重复
                Logger().getLogger().info("数据重复,film_name: %s,film_img_url:%s" % (item['film_name'], item['film_img_url']))
                pass
            else:
                # 插数据
                self.cursor.execute(
                    """insert into douban_movie_top_250(film_name, director_performer_name, film_year, film_country, film_type, film_rating, film_reviews_num, film_quato, film_img_url)
                        VALUE (%s, %s, %s, %s, %s, %s, %s, %s, %s)""",
                    (
                        item['film_name'],
                        item['director_performer_name'],
                        item['film_year'],
                        item['film_country'],
                        item['film_type'],
                        item['film_rating'],
                        item['film_reviews_num'],
                        item['film_quato'],
                        item['film_img_url']
                    )
                )
            # sql提交
            self.connect.commit()
        except Exception as error:
            Logger(logLevel='error').getLogger().error("数据插入数据库失败", error)
        return item
        
    def close_spider(self, spider):
        # 关闭数据库连接
        self.connect.close()

八、简单的反爬虫

    1、添加用户代理

        即添加 USER_AGENT,用于伪装浏览器         在 settings.py 中进行配置:
    # Crawl responsibly by identifying yourself (and your website) on the user-agent    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'

    2、不遵守 robots 协议

        在 settings.py 中进行配置:
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False

    3、请求间隙

        即防止请求过于频繁。
        在 settings.py 中进行配置,单位是秒:
    DOWNLOAD_DELAY = 0.25

    4、设置 cookies

        此处 Douban 的爬虫我们没有进行设置,github 中拉勾网的爬虫中有最简单设置。
        即在爬虫代码中设置 cookie,然后在每个 Request 请求中直接添加。
    yield scrapy.Request(job_url, cookies=self.cookie, meta={'item': item}, callback=self.parse_url)
        或者完美一点的做法,应该是在中间件中设置,即在 middlewares.py 文件中进行配置。

九、日志

    日后写一篇详细的介绍,此处暂不进行细说。
    自定义的简单的日志模块为项目中的 logger.py 文件,使用方法见注释。

十、运行

    1、查找可运行的 scrapy 项目

        scrapy list

    2、运行爬虫

        scrapy crawl douban

    3、运行爬虫并将 item 信息输出至文件

        scrapy crawl douban -o doubanData.json

    4、新建执行文件

        新建 run.py 执行文件
        文件内容为:
    from scrapy.cmdline import execute   
     
    execute(['scrapy', 'crawl', 'douban'])
        以后直接执行这个 python 文件即可。



十一、源码

    https://github.com/goldenJet/SpiderDemo/tree/master/Douban

    













本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: python scrapy 爬虫
最后更新:2018年12月10日

GoldenJet

爱折腾技术的90后漫威小死忠程序员一枚

点赞
< 上一篇
下一篇 >

文章评论

取消回复

通过电子邮件订阅博客

分类目录
  • BootStrap (2)
  • Bug集中营 (6)
  • Java web (3)
  • JavaScript (7)
  • Java基础 (17)
  • Java工具 (5)
  • Linux (3)
  • Python (3)
  • SpringBoot (14)
  • Spring基础 (8)
  • thymeleaf (1)
  • 娱乐 (3)
  • 小谈 (2)
  • 常用工具 (7)
  • 技术分析集 (5)
  • 技能 (10)
  • 源码 (4)
  • 科普类 (1)
  • 算法 (9)
  • 踩坑记 (5)
文章归档
  • 2020年11月 (1)
  • 2020年7月 (1)
  • 2020年4月 (2)
  • 2020年3月 (1)
  • 2020年1月 (1)
  • 2019年11月 (1)
  • 2019年10月 (1)
  • 2019年9月 (1)
  • 2019年8月 (1)
  • 2019年7月 (2)
  • 2019年5月 (2)
  • 2019年4月 (2)
  • 2019年3月 (3)
  • 2019年2月 (2)
  • 2019年1月 (2)
  • 2018年12月 (2)
  • 2018年11月 (3)
  • 2018年10月 (3)
  • 2018年9月 (2)
  • 2018年8月 (3)
  • 2018年7月 (2)
  • 2018年5月 (1)
  • 2018年4月 (3)
  • 2018年3月 (2)
  • 2018年2月 (3)
  • 2018年1月 (5)
  • 2017年12月 (2)
  • 2017年11月 (3)
  • 2017年10月 (1)
  • 2017年9月 (1)
  • 2017年8月 (1)
  • 2017年7月 (7)
  • 2017年6月 (5)
  • 2017年5月 (1)
  • 2017年4月 (2)
  • 2017年3月 (4)
  • 2017年2月 (2)
小伙伴友链
  • 前端驿站

COPYRIGHT © 2017-2023 嘎里三分熟. ALL RIGHTS RESERVED.

浙ICP备17005575号-1

浙公网安备 33010802009043号