Scrapy笔记

- 状态:doing

1.框架介绍

安利这个博客:
爬虫框架Scrapy的讲解

数据流动

对数据的处理流程如下:

2.实践

reference: 原创 Scrapy框架实战项目

写爬虫的时候,整体的流程如下:

2.1 建项目

scrapy startproject 一个项目名

2.2 创建spider文件

scrapy genspider [options] 用模板创建一个spider文件,记得要先进到工程的目录下❗️
例如,scrapy genspider example example.com ,其中的参数example是spider文件中的name,也是在运行这个爬虫时scrapy crawl 后跟的那个参数;example.com是spider文件中的start_urls,注意这里千万不要带http://❗️也不要带双引号❗️一般来说,example不能和第一步的项目名重名。

2.3 定义抓取字段

打开items.py文件,把你想要抓取的字段用scrapy.Field()这种形式定义出来,例如:

import scrapy

class TubatuItem(scrapy.Item):
    # define the fields for your item here like:
    content_url = scrapy.Field()  # 请求的url地址
    image_urls = scrapy.Field()

2.4 写spider.py

开始写spider文件中的爬虫步骤,spider文件的整体结构是这样的:

import scrapy

class TubatuTestSpider(scrapy.Spider):
    name = "tubatu_test"
    astart_urls = ['https://xiaoguotu.to8to.com/tuce//']

    def parse(self, response):
        pass

这几个参数或者方法的意思如下:
name = "" :这个爬虫唯一标志的名称;
allow_domains = []:规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略;
start_urls = ():第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用。作用是解析返回的网页数据(response.body)、提取结构化数据(生成item),以及生成需要下一页的URL请求。

我们在parse文件里写爬虫的操作,存在以下问题:
①例如对于一级url的页面来说,我们可能需要抓取页面上的文本信息和二级url链接,怎么对二级url链接中的页面内容再次进行抓取呢?这里,我们使用yield方法发起请求,并定义一个新的页面分析方法作为回调函数去处理二级url中的响应:

yield scrapy.Request(url=二级url,callback=self.basicInfo())

这是个Request对象,scrapy会先去请求这个二级url,然后去执行回调函数。(request加入爬取队列)

②一级url不止一个网页,如果需要翻页获取全部的信息怎么处理?
思路是:找到下一页的url,然后依然使用yield方法发请求,只不过这里传参的url换成下一页的url(我的理解:如果页面存在“下一页”按钮,把这个按钮带的href值传进来;还有一种情况是当前页面的url会有规律的进行变化,例如_1,_2这种就比较简单了),回调函数传入parse方法。(item类型则使用pipeline处理)
但是要记得,这个yield的执行需要放在当前页面全部处理完成之后❗️

③既然这个parse提取的是Item类的数据,那我们自己写对二级url处理的时候怎么搞呢?
在写scrapy项目的第3步,我们在items.py文件里定义的就是这种Item类,它类似于Python当中的词典,我们需要将解析得到的数据放到这类中,因此需要先实例化item,然后以字典的方式进行值的传递,例如:

tubatu_info = TubatuItem()
tubatu_info['content_url']='abcdefg.com' 

进一步来说,如果我们的函数返回的是item类型的函数,需要使用yield方法将其送到pipeline中,决定它下一步是文件存储?还是数据库存储?等等。。

好啦,我们写完spider的内容了,接下来就是怎么存储的问题咯

2.5 写pipelines.py

由于我现在没有装数据库,打算先搞一下文件和图片存储的pipeline方式。
item pipiline组件是一个独立的Python类,其中每个item pipeline组件都需要调用该process_item(),这个方法返回的是Item对象。此方法有两个参数,一个是item,即要处理的Item对象,另一个参数是spider
原始是这个样子的,我们需要什么方式的pipeline就需要定制什么样的pipeline组件:

class SomethingPipeline(object):
    def __init__(self):    
        # 可选实现,做参数初始化等
        # doing something

    def process_item(self, item, spider):
        # item (Item 对象) – 被爬取的item
        # spider (Spider 对象) – 爬取该item的spider
        # 这个方法必须实现,每个item pipeline组件都需要调用该方法,
        # 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
        return item

    def open_spider(self, spider):
        # spider (Spider 对象) – 被开启的spider
        # 可选实现,当spider被开启时,这个方法被调用。

    def close_spider(self, spider):
        # spider (Spider 对象) – 被关闭的spider
        # 可选实现,当spider被关闭时,这个方法被调用

当然这上面的后两个方法我还没有试过,也不知道是啥意思。❓
定义文本存储的组件如下

class TubatuPipeline(object):
    def process_item(self, item, spider):
        # print(item)
        # print(type(item))
        # print(len(item))
        with open('tu8tu.txt','a',encoding='utf-8') as f:
            f.write(str(item['content_id'])+'    '+item['content_name']+'    '+item['content_url']+'     '+str(item['nick_name'])+'   '+item['pic_name']+'     '+item['pic_url']+'\n')
        return item

定义图片存储的组件如下:(p.s这个是抄的,还没来得及研究,明天看)

class TubatuImagePipeline(ImagesPipeline):
    # def get_media_requests(self, item, info):
    #     # 根据image_urls中指定的url进行爬取,该方法默认即可
    #     pass

    def item_completed(self, results, item, info):
        # 图片下载完成之后,处理结果的方法,返回的是一个二元组
        # 返回的格式:(success, image_info_or_failure)  第一个元素表示图片是否下载成功;第二个元素是一个字典,包含了image的信息
        image_paths = [x["path"] for ok, x in results if ok]    # 通过列表生成式
        if not image_paths:
            raise DropItem("Item contains no images")   # 抛出异常,图片下载失败(注意要导入DropItem模块)
        return item

    def file_path(self, request, response=None, info=None):
        # 用于给下载的图片设置文件名称
        url = request.url
        file_name = url.split("/")[-1]  # 通过 / 来进行分割,选取最后一个
        # 如:"https://pic.to8to.com/case/2018/08/27/20180827165930-fac62168_284.jpg"
        # 拆分后为:20180827165930-fac62168_284.jpg
        return file_name

定义csv组件:

class TencentCSVPipeline(object):
    def __init__(self):
        store_file  = os.path.dirname(__file__)+'/spiders/hireInfo.csv'
        self.file = open(store_file,"a+",encoding='utf-8',newline='')
        self.writer = csv.writer(self.file)
        self.writer.writerow(['title', 'bu', 'place', 'type', 'time'])
    def process_item(self, item, spider):
        self.writer.writerow([item['hire_title'],item['hire_bu'],item['hire_place'],item['hire_type'],item['hire_time']])
        return item

定义完组件之后,需要将它的类添加到 settings.py文件ITEM_PIPELINES 配置,例如:

ITEM_PIPELINES = {
   # 'tubatu.pipelines.TubatuPipeline': 300,
    'tubatu.pipelines.TubatuImagePipeline': 300,//数字越小,组件的优先级越高
}

2.6 反反爬虫(to be continued)——middlewares.py

下载中间件(Downloader Middlewares)的作用:

  • 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
  • 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
    下载中间件需要去settings.py里激活。
    自己定义一个,格式如下:
class RandomUserAgent(object):
    def process_request(self, request, spider):
      #要是这么写,需要在settings.py里定义一批USER_AGENTS。
        useragent = random.choice(USER_AGENTS)

        request.headers.setdefault("User-Agent", useragent)

最后需要设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类

2.7 爬虫项目的运行方式

如果觉得每次都需要开个命令行太麻烦,可以在当前目录下建一个main.py文件,写入下面代码:

from scrapy import cmdline
cmdline.execute("scrapy crawl 项目名称".split())  

2.8 零碎知识

2.8.1 Request

参数如下:

url: 就是进行下一步处理的url;
callback: 请求返回的Response由哪个函数来处理;
method: 请求一般不需要指定,默认GET方法,可设置为"GET", "POST", "PUT"等,且保证字符串大写;
headers:不多说了;
meta: 比较常用,在不同的请求之间传递数据使用的,字典类型。在回调函数中使用时,用response.request.meta[]的形式进行取值。
encoding: 使用默认的 'utf-8' 就行;
dont_filter: 表明该请求不由调度器过滤。这是当你想使用多次执行相同的请求,忽略重复的过滤器。默认为False;
errback: 指定错误处理函数

2.8.2 scrapy.Spider类

参数如下:

__init__() : 初始化爬虫名字和start_urls列表;
其中包含的属性有:

  • name:定义spider名字的字符串。
  • allowed_domains:包含了spider允许爬取的域名(domain)的列表,可选。
  • start_urls:初始URL元祖/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。

start_requests() :调用make_requests_from url生成Requests对象交给Scrapy下载并返回response;

  • 该方法必须返回一个可迭代对象(iterable),一般是[]这样的形式
  • 当spider启动爬取并且未指定start_urls时,该方法被调用。

parse(): 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。

  • 当请求url返回网页没有指定回调函数时,默认的Request对象回调函数

2.8.3 LinkExtractors

常用于全站爬取时获取url,使用时一般结合Crawler和Rule,提取匹配,并使用spider的方法进行分析;并跟进链接。Rule里的callback 千万不能写 parse❗️
使用方法: python爬虫scrapy的LinkExtractor

2.8.4 模拟登陆,发送POST请求写法

import scrapy

class LoginSpider(scrapy.Spider):
    name = 'example.com'
    start_urls = ['http://www.example.com/users/login.php']

    def parse(self, response):
        return scrapy.FormRequest.from_response(
            response,
            formdata={'username': 'john', 'password': 'secret'},
            callback=self.after_login
        )

    def after_login(self, response):
        # check login succeed before going on
        if "authentication failed" in response.body:
            self.log("Login failed", level=log.ERROR)
            return

2.8.5 ItemLoader(还未看...❗️)

Scrapy爬虫入门教程七 Item Loaders(项目加载器)

3.实践demo

github:
https://github.com/mikiihuang/spider_learn
这几天计划写几个spider项目熟悉一下,都会同步更新在这个仓库里~欢迎大佬指教~

需要登陆的:
Scrapy模拟登陆豆瓣抓取数据

4.遇到的问题

Scrapy爬取下来的数据不全,为什么总会有遗漏
爬虫出现Forbidden by robots.txt
Scrapy框架爬取有验证码的登录网站

你可能感兴趣的:(Scrapy笔记)