【Rollo的Python之路】Python Scrapy学习笔记(一)

时间:2022-12-12 21:23:29

Scrapy:

Scrapy使用了Twisted作为框架,Twisted有些特殊的地方是它是事件驱动的,并且比较适合异步的代码。对于会阻塞线程的操作包含访问文件、数据库或者Web、产生新的进程并需要处理新进程的输出(如运行shell命令)、执行系统层次操作的代码(如等待系统队列),Twisted提供了允许执行上面的操作但不会阻塞代码执行的方法。

items.py 负责数据模型的建立,类似于实体类。
middlewares.py 自己定义的中间件。
pipelines.py 负责对spider返回数据的处理。
settings.py 负责对整个爬虫的配置。
spiders目录 负责存放继承自scrapy的爬虫类。
scrapy.cfg scrapy基础配置

 

今天学习了一下scrapy的抓取,做了第一个学习测试:

 

Spiders

import scrapy

from quotetoturials.items import QuoteItem

class QuoteSpider(scrapy.Spider):
    name = 'quote'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ["http://quotes.toscrape.com/"]

    def parse(self, response):
        quotes = response.css('.quote')
        for quote in quotes:
            item = QuoteItem()
            text = quote.css('.text::text').extract_first()
            author = quote.css('.author::text').extract_first()
            tags = quote.css(".tags .tag::text").extract()
            item['text'] = text
            item['author'] = author
            item['tags'] = tags
            yield item
next
= response.css('.pager .next a::attr(href)').extract_first() #css提取下一页 url = response.urljoin(next) #urljoin 做URL yield scrapy.Request(url=url,callback=self.parse) #callback 回调函数

 

这里面有几个知识点:

  1.1 scrapy 里面的spiders 的QuoteSpider必须继承父类:scrapy.Spider。

  1.2 css 提取器规则,要学会

  1.3 xpath 方法提取也是可以的

  1.4 urljoin方法:这个方法是用来构建绝对URL的方法。

  python3对urllib和urllib2进行了重构,拆分成了urllib.request, urllib.response, urllib.parse, urllib.error等几个子模块,这样的架构从逻辑和结构上说更加合理。

urljoin现在对应的函数是urllib.parse.urljoin

所以引入urljoin 是:

from urllib.parse import urljoin

  

from urllib.parse import urljoin

url1 = urljoin("http://www.baidu.com/1/aaa.html","bbbb.html")
#直接替换url的最后一个'/'
print(url1)

url2 = urljoin("http://www.baidu.com/1/aaa.html","2/bbbb.html")
print(url2) #直接替换url的最后一个'/'

url3 = urljoin("http://www.baidu.com/1/aaa.html","/2/bbbb.html")
print(url3) #直接替换url的最后二个'/'

url4 = urljoin("http://www.baidu.com/1/aaa.html","http://www.baidu.com/3/ccc.html")
print(url4) #第二个是完整URL就直接用第二个

url5 = urljoin("http://www.baidu.com/1/aaa.html","http://www.baidu.com/ccc.html")
print(url5) #第二个是完整URL就直接用第二个

url6 = urljoin("http://www.baidu.com/1/aaa.html","javascript:void(0)")
print(url6)  #javascript直接用

执行结果:

http://www.baidu.com/1/bbbb.html
http://www.baidu.com/1/2/bbbb.html
http://www.baidu.com/2/bbbb.html
http://www.baidu.com/3/ccc.html
http://www.baidu.com/ccc.html
javascript:void(0)

  1.5 parse方法:

  parse(self,response):当请求url返回网页没有指定回调函数,默认的Request对象的回调函数,用来处理网页返回的response,和生成的Item或者Request对象,以下分析一下parse()方法的工作机制:

1.因为使用的yield,而不是return,parse函数将会当做一个生成器使用,scrapy会注意调用parse方法中生成的结果,并且判断该结果是一个什么样的类型

2.如果是request则会加入爬取队列中,如果是item类型则会使用pipeline处理,其他类型则会返回错误信息

3.scrapy取到第一部分的request不会立马就去发送request,只是将这个request放到队列中,然后接着从生成器中获取

4.取完了第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline中处理

5.parse方法作为回调函数(callback),赋值给Request,指定parse()方法处理这些请求scrapy.Request(url,callback=self.parse)

6.Request对象经过调度,执行生成scrapy.http.response()响应对象,并送回parse()方法,直到调度器中没有Requset(递归的思路)

7.取尽之后,parse()工作结束,引擎再根据对列和pipeline中的内容去执行相应的操作

8.程序在取得各个页面的items前,会先处理完之前所有的request对列的请求,然后再提取items

  1.6 callback传参:可以用lambda,可以用Request.meta传参

 

items.py

import scrapy

class QuoteItem(scrapy.Item):
    # define the fields for your item here like:
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

 

这里面要了解 Field()这个对象:

①Field对象指明了每个字段的元数据(任何元数据),Field对象接受的值没有任何限制

②设置Field对象的主要目就是在一个地方定义好所有的元数据

③注意,声明item的Field对象,并没有被赋值成class属性。(可通过item.fields进行访问)

④Field类仅是内置字典类(dict)的一个别名,并没有提供额外的方法和属性。被用来基于类属性的方法来支持item生命语法。

class dict(object):
    """
    dict() -> 创建空字典
    dict(mapping)  -> 从映射对象的键值对中初始化新字典{"key":value}
    dict(iterable) -> 从迭代器初始化字典 d[key] = value
    dict(**kwargs) -> 使用字典初始化新字典
    """
    
    # 1.清除字典内部所有内容 -> 空字典
    def clear(self):
        pass
 
    # 2.字典dict的复制
    def copy(self):
        """ dict_copy = D.copy()"""
        pass
 
    # 3.静态方法。返回一个新的字典,其中包含来自迭代器的键,以及对应的值
    @staticmethod # known case
    def fromkeys(*args, **kwargs): # real signature unknown
        pass
 
    # 4.返回指定键k的值,如果值不在字典中返回d的值
    def get(self, k, d=None): # real signature unknown; restored from __doc__
        pass
 
    # 5.以字典形式返回可遍历的(键,值)对
    def items(self): # real signature unknown; restored from __doc__
        pass
 
    # 6.以列表形式返回一个字典所有的键
    def keys(self): # real signature unknown; restored from __doc__
        pass
        
    # 7.删除字典给定键k所对应的值,返回值为被删除的值。k值必须给出。否则,返回d值。
    def pop(self, k, d=None): # real signature unknown; restored from __doc__
        pass
 
    # 8.随机返回并删除字典中的一对键和值
    def popitem(self): # real signature unknown; restored from __doc__
        pass
 
        
    # 9.和get()类似, 但如果键k不存在于字典中,将会添加键并将值设为d
    def setdefault(self, k, d=None): # real signature unknown; restored from __doc__
        pass
 
    # 10.将字典2中的键值对更新入调用字典中
    def update(self, E=None, **F): # known special case of dict.update
        """
        If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
        If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
        In either case, this is followed by: for k in F:  D[k] = F[k]
        """
        pass
 
    # 11.以字典的形式返回字典中所有的值
    def values(self): # real signature unknown; restored from __doc__
        """ D.values() -> an object providing a view on D's values """
        pass
 
    # 12.内置方法。判断键是否在字典中,在返回True,不在返回False
    def __contains__(self, *args, **kwargs): # real signature unknown
        pass
        
    # 13.删除字典中对应k键的值
    def __delitem__(self, *args, **kwargs): # real signature unknown
        """ Delete self[key]. """
        pass
 
    # 14.返回存在Value值得字典
    def __eq__(self, *args, **kwargs): # real signature unknown
        """ Return self==value. """
        pass
        
    def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass
 
    def __getitem__(self, y): # real signature unknown; restored from __doc__
        """ x.__getitem__(y) <==> x[y] """
        pass
 
    def __ge__(self, *args, **kwargs): # real signature unknown
        """ Return self>=value. """
        pass
 
    def __gt__(self, *args, **kwargs): # real signature unknown
        """ Return self>value. """
        pass
 
    def __init__(self, seq=None, **kwargs): # known special case of dict.__init__
        pass
 
    def __iter__(self, *args, **kwargs): # real signature unknown
        """ Implement iter(self). """
        pass
 
    def __len__(self, *args, **kwargs): # real signature unknown
        """ Return len(self). """
        pass
 
    def __le__(self, *args, **kwargs): # real signature unknown
        """ Return self<=value. """
        pass
 
    def __lt__(self, *args, **kwargs): # real signature unknown
        """ Return self<value. """
        pass
 
    @staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass
 
    def __ne__(self, *args, **kwargs): # real signature unknown
        """ Return self!=value. """
        pass
 
    def __repr__(self, *args, **kwargs): # real signature unknown
        """ Return repr(self). """
        pass
 
    def __setitem__(self, *args, **kwargs): # real signature unknown
        """ Set self[key] to value. """
        pass
 
    def __sizeof__(self): # real signature unknown; restored from __doc__
        """ D.__sizeof__() -> size of D in memory, in bytes """
        pass
 
    __hash__ = None

 

pipelines.py

class TextPipeline(object):

    def __init__(self):
        self.limit = 50

    def process_item(self, item, spider):
        if item['text']:
            if len(item['text']) > self.limit:
                item['text'] = item['text'][0:self.limit].rstrip() + '...'
            return item
        else:
            return DropItem('Missing Text')