Python_Scrapy爬虫框架

首先回忆一下爬虫爬取网页的步骤:

  • 确定url地址;
  • 获取页面信息(urllib,requests);
  • 解析页面提取需要的数据(正则表达式, bs4, xpath);
  • 保存到本地(csv,json,pymysql,redis);
  • 清洗数据(删除不必要的内容 ----> 正则表达式);
  • 对数据词云分析(wordcloud + jieba)。

获取页面信息每个爬虫都会使用,都要重复去写,重复的设置头部信息user-agent, proxy…等等,所以Scrapy就变得有用了。

Scrapy爬虫框架

基本概念

  • Scrapy:Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。

  • Scrapy 吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。

  • Scrap是碎片的意思,这个Python的爬虫框架叫Scrapy。

优势

  • 用户只需要定制开发几个模块,就可以轻松实现爬虫,用来抓取网页内容和图片,非常方便;
  • Scrapy 使用了Twisted异步网络框架来处理网络通讯,加快网页下载速度,不需要自己实现异步框架和多线程等,并且包含了各种中间件接口,灵活完成各种需求

架构流程

架构图:
在这里插入图片描述
主要组件:

  • 引擎(Scrapy):用来处理整个系统的数据流,触发事务(框架核心);

  • 调度器(Scheduler):用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址;

  • 下载器(Downloader):用于下载网页内容,并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的);

  • 爬虫(Spiders):爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面;

  • 项目管道(Pipeline):负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

运作流程:
代码写好,程序开始运行…

  • 1 引擎:Hi!Spider, 你要处理哪一个网站?
  • 2 Spider:老大要我处理 xxxx.com
  • 3 引擎:你把第一个需要处理的URL给我吧。
  • 4 Spider:给你,第一个URL是 xxxxxxx.com
  • 5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  • 6 调度器:好的,正在处理你等一下。
  • 7 引擎:Hi!调度器,把你处理好的request请求给我。
  • 8 调度器:给你,这是我处理好的request。
  • 9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求。
  • 10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  • 11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  • 12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  • 13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  • 14 管道调度器:好的,现在就做!

注意:只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)

流程分析:

  • 确定url地址:http://www.imooc.com/course/list ;----> (spider)
  • 获取页面信息(urllib,requests);----> (scrapy中我们不用处理) ----> (Downloader)
  • 解析页面提取需要的数据(正则表达式, bs4, xpath);----> (spider)
    (课程标题, 学习人数, 课程描述, 课程链接, 课程的图片url)
  • 保存到本地(csv,json,pymysq,redis);----> (pipeline)

爬虫步骤(5步)

以爬取mooc课程信息为例

新建项目

1.新建一个新的爬虫项目

scrapy  startproject mySpider			

查看爬虫框架的树结构

cd mySpider
tree		

├── mySpider
│   ├── __init__.py
│   ├── items.py            # 提取的数据信息
│   ├── middlewares.py      # 中间键
│   ├── pipelines.py        # 管道,如何存储数据
│   ├── __pycache__
│   ├── settings.py         # 设置信息
│   └── spiders             # 爬虫(解析页面的信息)
│       ├── __init__.py
│       └── __pycache__
└── scrapy.cfg

2.创建一个爬虫

scrapy  genspider  mooc "www.imooc.com"
明确目标 (items.py

明确你想要抓取的目标 ----> 定义爬取的items内容

class CourseItem(scrapy.Item):
	# Item对象是一个简单容器, 保存爬取到的数据, 类似于字典的操作
   
    # 课程标题
    title = scrapy.Field()
    # 学习人数
    study_num = scrapy.Field()
    # 课程描述
    introduction = scrapy.Field()
    # 课程url
    url = scrapy.Field()
    # 课程图片url
    img_url = scrapy.Field()
制作爬虫 (spiders/ mooc.py

制作爬虫开始爬取网页 ----> 编写spider代码,解析网页

import scrapy
from mySpider.items import CourseItem


class MoocSpider(scrapy.Spider):
	# name: 用于区别爬虫, 必须是唯一的
    name = 'mooc'
    # 允许爬取的域名;其他网站的页面直接跳过
    allowed_domains = ['www.imooc.com']
    # 爬虫开启时第一个放入调度器的url地址
    start_urls = ['http://www.imooc.com/course/list']

    # 函数被调用时,每个初始url完成下载后,返回一个响应
    def parse(self, response):
    	"""分析响应数据, 提取需要的数据items以及生成下一步需要处理的url地址请求"""
        
        # 用来检测代码是否到达指定位置,并用来调试并解析页面信息
        # from scrapy.shell import inspect_response
        # inspect_response(response,self)

        # 1.实例化对象
        course = CourseItem()
        # 2.分析响应的内容(scrapy分析页面使用xpath语法),获取每个课程的信息
        courseDetails = response.xpath('//div[@class="course-card-container"]')
        for courseDetail in courseDetails:

            # 课程标题
            course['title'] = courseDetail.xpath('.//h3[@class="course-card-name"]/text()').extract()[0]

            # 学习人数
            course['study_num'] = courseDetail.xpath('.//span/text()').extract()[1]

            # 课程描述
            course['introduction'] = courseDetail.xpath('.//p[@class="course-card-desc"]/text()').extract()[0]

            # 课程链接:先获取 /learn/9 ----> https://www.imooc.com/learn/9
            course['url'] = 'https://www.imooc.com' + courseDetail.xpath('.//a/@href').extract()[0]

            # 课程图片链接:http://img.mukewang.com/529dc3380001379906000338-240-135.jpg
            course['img_url'] = 'http:' + courseDetail.xpath('.//img/@src').extract()[0]

            yield course    

            # url跟进,获取下一页 ----> a标签是否有链接href
            url = response.xpath('.//a[contains(text(),"下一页")]/@href').extract()[0]
            if url:
            	# 构建新的url
                page ='http://www.imooc.com' + url
                yield scrapy.Request(page,callback=self.parse)
存储内容 (pipelines.py

设计管道存储爬取内容

import json
import pymysql
import scrapy

from mySpider.settings import MOOC_FILENAME, MOOC_CSVFILENAME
from scrapy.pipelines.images import ImagesPipeline

class MyspiderPipeline(object):
    """将爬取的信息保存为Json格式"""

    def __init__(self):
        self.f = open(MOOC_FILENAME, 'w')

    def process_item(self, item, spider):
        # 读取item中的数据,并转为json格式
        line = json.dumps(dict(item), ensure_ascii=False, indent=4)
        self.f.write(line + '\n')
        # 一定要加,返回给调度器
        return item

    def close_spider(self, spider):
        """当爬虫全部爬取结束时执行的函数"""
        self.f.close()


class CsvPipeline(object):
    """将爬取的信息保存为Csv格式"""

    def __init__(self):
        self.f = open(MOOC_CSVFILENAME, 'w')

    def process_item(self, item, spider):
        item = dict(item)
        self.f.write('{0},{1},{2},{3},{4}\n'.format(item['title'], item['study_num'], item['introduction'], item['url'],
                                                    item['img_url']))
        return item

    def close_spider(self, spider):
        """当爬虫全部爬取结束时执行的函数"""
        self.f.close()


class MysqlPipeline(object):
    """将爬取的信息保存到数据库中"""
    # 首先创建一个数据库 Mooc
    def __init__(self):
        self.conn = pymysql.connect(
            host='localhost',
            user='root',
            password='westos',
            db='Mooc',
            charset='utf8'
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        # item 是一个对象
        item = dict(item)
        info = (item['title'], item['study_num'], item['introduction'], item['url'], item['img_url'])
        insert_sqli = "insert into moocinfo values('%s','%s','%s','%s','%s');" % (info)
        self.cursor.execute(insert_sqli)
        self.conn.commit()
        return item

    def open_spider(self, spider):
        """开始爬虫时执行的函数"""
        create_sqli = "create table if not exists moocinfo (title varchar(20),study_num int,introduction varchar(300),url varchar(200),img_url varchar(500));"
        self.cursor.execute(create_sqli)

    def close_spider(self, spider):
        """当爬虫全部爬取结束时执行的函数"""
        self.cursor.close()
        self.conn.close()

class ImagePipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        # 返回一个request请求,包含图片的url地址
        yield scrapy.Request(item['img_url'])

    # 当下载请求完成后执行的函数/方法
    def item_completed(self, results, item, info):
        # 获取下载的地址
        img_path = [x['path'] for ok,x in results if ok]
        if not img_path:
            raise Exception('不包含图片')
        else:
            return item
配置文件(settings.py

最后,一定不要忘记设置scrapy的配置文件

# 变量可以定义在settings中
MOOC_FILENAME = 'mooc.txt'
MOOC_CSVFILENAME = 'mooc.csv'

# 设置管道的位置和优先级
ITEM_PIPELINES = {
    # 管道位置:优先级(0~1000,数字越小,优先级越高)
   'mySpider.pipelines.MyspiderPipeline': 300,
   'mySpider.pipelines.CsvPipeline': 400,
   'mySpider.pipelines.MysqlPipeline': 500,
   'mySpider.pipelines.ImagePipeline': 200,
}

# 下载图片时,设置图片存储的路径
IMAGES_STORE = '/home/kiosk/PycharmProjects/2018.12.30/day17/img'

运行爬虫

以爬取mooc课程信息为例

运行爬虫命令

scrapy crawl mooc

当运行出错时,用来调试代码
检测代码是否到达指定位置,并用来调试并解析页面信息

from scrapy.shell import inspect_response
inspect_response(response,self)

爬取的信息以json格式保存到 mooc.txt:
在这里插入图片描述
爬取的信息以csv格式保存到 mooc.csv:
在这里插入图片描述
爬取的信息保存到数据库:
在这里插入图片描述

根据爬取的课程图片url将图片下载本地;
在这里插入图片描述

反爬虫策略

策略一:设置download_delay
  • 作用:设置下载的等待时间,大规模集中的访问对服务器的影响最大,相当与短时间中增大服务器负载;

  • 缺点:下载等待时间长,不能满足段时间大规模抓取的要求,太短则大大增加了IP被封的几率;

  • 实现:settings.py ----> DOWNLOAD_DELAY = 3(每下载一个页面, 等待3秒)。

策略二:禁止cookies
  • Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、进行 session跟踪而储存在用户本地终端上的数据(通常经过加密);
  • 作用:禁止cookies也就防止了可能使用cookies识别爬虫轨迹的网站得逞;
  • 实现:settings.py ----> COOKIES_ENABLES=False
策略三:设置用户代理
  • 为什么使用?
    scrapy本身是使用Scrapy/0.22.2来表明自己身份的。这也就暴露了自己是爬虫的信息。
  • user agent,是指包含浏览器信息、操作系统信息等的一个字符串,也称之为一种特殊的网络协议。服务器通过它判断当前访问对象是浏览器、邮件客户端还是网络爬虫。
  • 实现:settings.py ----> USER_AGENT = ‘Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0’

拓展:设置User-Agent的中间键
middlewares.py 中设置User-Agent的中间键

class UserAgentMiddleware(object):
    def __init__(self):
        self.user_agent = [
            'Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0',
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        ]
    def process_request(self, request, spider):
        ua = random.choice(self.user_agent)
        if ua:
            # 此行仅为了测试, 真实场景不要打印, 会影响爬虫的效率
            # print("当前使用的用户代理: %s" %(ua))
            request.headers.setdefault('User-Agent', ua)
策略四:设置代理IP中间键
  • web server应对爬虫的策略之一就是直接将你的IP或者是整个IP段都封掉禁止访问,这时候,当IP封掉后,转换到其他的IP继续访问即可。

middlewares.py 中设置代理IP的中间键

class ProxiesMiddleware(object):
    def __init__(self):  
        self.proxies = [
            'http://116.209.54.221:9999',
            "https://111.177.183.212:9999"
        ]
    def process_request(self, request, spider):
        """当发起请求"""
        # 随机获取一个代理IP
        proxy = random.choice(self.proxies)    
        if proxy:
            # 此行仅为了测试, 真实场景不要打印, 会影响爬虫的效率
            # print("当前使用的代理IP: %s" %(proxy))
            request.meta['proxy'] = proxy
策略五:分布式爬虫Scrapy+Redis+MySQL
  • 多进程
  • Scrapy-Redis则是一个基于Redis的Scrapy分布式组件。
  • 它利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生rapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。

猜你喜欢

转载自blog.csdn.net/King15229085063/article/details/87795715