Logo

郎哥编程

数据的定义与处理

2020-12-07 196

爬虫爬取数据的主要目的就是从非结构性的网页数据提取结构化的数据,结构化数据或存储到文件、或存储到数据库。

1、爬取数据处理过程

011.png

上图给出了爬虫程序对非结构化网页数据的处理过程。

爬虫程序获取非结构化网页数据后,会对非结构化的网页数据进行清洗,清洗过程包网页数据的选取、过滤无关数据(无关数据也称为数据噪声),然后对提取的数据进一步处理,处理后的数据需要满足结构化数据的要求。

非结构化网页数据经过数据清洗后,会填充到Scrapy框架的Item结构,Item是一个类,它允许开发者定义自己的结构化数据,最后通过Scrapy框架的数据管道(Item Pipeline)对Item数据进一步处理,最后写入到文件或数据库。

结构化数据的定义与爬虫爬取数据的目的有关,爬取目的不同,定义的数据结构也不同。例如百度新闻爬虫,目的是获取百度新闻的新闻条目,因此在Item定义的结构化数据为:

import scrapy
class NewsbaiduItem(scrapy.Item):
    # 定义要爬取的数据:
    # 文章标题
    news_title = scrapy.Field()
    # 文章链接
    news_link = scrapy.Field()

2、 定义结构化数据

自定义结构化数据

开发者自定义的结构化数据要继承Item类。例如:若要爬取图书网站,下面定义了存储图书信息的结构化数据。

import scrapy
# 定义结构化数据Book类
# Book类继承scrapy.Item类
class Book(scrapy.Item):
    # 定义name数据项,存储图书名称
    name = scrapy.Field()
    # 定义author数据项,存储图书作者
    author = scrapy.Field()
    # 定义brief数据项,存储图书简介
    brief = scrapy.Field()
    # 定义price数据项,存储图书价格
    price = scrapy.Field()

Book类是自定义的结构化数据,继承于scrapy.Item类,Book类有四个数据项(数据项也可以称为字段),分别是name、author、brief、price,数据项的类项是字典。

Item类有一个很重要的属性Field,Field是实例化的字典对象,Field属性可以直接使用scrapy.Field()来访问。

Item类内置了字典对象fields,执行下面的语句后:

name = scrapy.Field()

Item类内置的字典对象fields会或添加一个键值对,键是字符串name,值是一个空的字典对象。

字典对象fields可以直接通过实例化的Item类来访问。

案例代码:

import scrapy
# 定义结构化数据Book类
# Book类继承scrapy.Item类
class Book(scrapy.Item):
    # 定义name数据项,存储图书名称
    name = scrapy.Field()
    # 定义author数据项,存储图书作者
    author = scrapy.Field()
    # 定义brief数据项,存储图书简介
    brief = scrapy.Field()
    # 定义price数据项,存储图书价格
    price = scrapy.Field()
 
b = Book(name='Desktop PC', author="赵三",brief="图书简介",price=19.2)
print(b)
print(b.fields)

案例代码定义了Book类,实例化的Book类赋值给变量b,b是一个实例化的Book对象,print(b)输出Book类的字符串内容,print(b.fields)输出Book类内置的fields字典对象。

程序执行结果如下所示:

{'author': '赵三', 'brief': '图书简介', 'name': 'Desktop PC', 'price': 19.2}
{'author': {}, 'brief': {}, 'name': {}, 'price': {}}
>>>

自定义结构化数据项的访问

案例中的Book类继承scrapy.Item类,在Book类定义的数据项为Item类内置字典对象fields的元素。因此对数据项的访问和对字典对象元素的访问是相同的操作。

(1)设置数据项的值

数据项的名称为字典对象元素的key,数据项的值为字典对象元素的value。

# 实例化Book对象
b = Book()
# 赋值name数据项
b["name"] = "Desktop PC"
# 赋值author数据项
b["author"] = "赵三"
# 赋值brief数据项
b["brief"] = "图书简介"
# 赋值price数据项
b["price"] = 19.9

(2)获取数据项的值

获取数据项的值实际就是字典对象元素值的获取操作,使用Book类实例对象的名称就可以直接获取数据项的值。

b["name"]
b.get('name')

其中b是Book类实例对象的名称,name是数据项名称,b["name"]返回name对应的值,get()是字典类型获取元素值的方法。

可以使用成员运算符来判断数据项是否在实例对象b中

"name" in b

其中b是Book类实例对象的名称,name是数据项名称,in是成员运算符。若b存在数据项名称name,返回True,否则返回False。

遍历b中所有的数据项

for key in b:
    print(b[key],end=",")

其中b是Book类实例对象的名称。

3、 数据的处理ItemLoader类

Scrapy的ItemLoader类的实例对象可以用来对数据进行处理,ItemLoader类在scrapy.contrib.loader模块内,爬虫程序要使用ItemLoader类,需要导入ItemLoader类。

import scrapy.contrib.loader.ItemLoader as ItemLoader

ItemLoader类的构造方法如下表所示:

012.png

注释(1)

构造方法声明:

ItemLoader([item, selector, response, ]**kwargs)

方法返回ItemLoader类的实例对象,若没有参数给出,返回一个默认的实例化对象。若给出了item、selector参数,返回的实例对象可以使用selector清洗数据并填充到item。

参数item是Item类的实例化对象,若传入selector或selector,该参数必须提供。

参数selector是Selector类的实例化对象,该对象包含了要清洗的数据。若给出了selector参数,response参数一般被忽略。

参数response是请求返回的实例对象。若没有给出selector参数,该参数用来清洗数据并填充到item。若给出了selector参数,该参数会被忽略。

参数kwargs是可选的关键字参数,为数据的处理提供更多的选项。

ItemLoader类的主要方法如下表所示:

013.png

注释(1)

方法声明:

get_value(value, *processors, **kwargs)

对value数据进行处理,清洗通过processors和kwargs进行,返回处理后的数据。

参数value是要处理的数据,可以是网页内容,也可以是其它数据,数据类型一般为字符串。

参数processors是数据处理器,也是回调函数。Scrapy提供了一些内置的处理过程,用来对数据进行处理,后面会介绍这些内置的处理器。processors也可以是自定义的数据处理器。可以传入多个数据处理器,Scrapy会按顺序传递处理过的value,并执行这些数据处理器。

参数kwargs是关键字参数,可以传入正则表达式,在调用数据处理器之前从value中提取数据,提取后的数据再传递给数据处理器。

例如:re = “name: (.+)” 用于提取value中name:后的的内容。

案例代码:

# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 实例化ItemLoader类
loader = ItemLoader()
# 定义待处理的value数据
value = "<img src=\"image3_thumb.jpg\">"
# 定义处理函数
# 函数识别图片格式
def process_img(v):
    # 获取图片的扩展名
    extension = v[0].split(".")[1]
    if extension.lower() == "jpg":
        v[0] = "JPG图片:" + v[0]
    if extension.lower() == "png":
        v[0] = "PNG图片:" + v[0]
    if extension.lower() == "bmp":
        v[0] = "BMP图片:" + v[0]
    return v
ret = loader.get_value(value,process_img,re="img.*src=\"(.+?\.[a-z]+)\"")
print(ret)

案例代码自定义了process_img数据处理器,用于识别图片的类型,并在图片文件名称前面添加识别的图片类型。

get_value()方法传入的正则表达式用于提取网页img标签的src属性内容,并传入了自定义的数据处理器。get_value()方法会先用正则表达式对value数据进行提取,然后再调用process_img处理器对提取后的内容进行处理。

注释(2)

方法声明:

get_xpath(xpath, *processors, **kwargs)

该方法通过xpath表达式,从ItemLoader关联的选择器中提取字符串列表,并对提取的字符串列表进行数据处理。

参数xpath是xpath表达式。

参数processors和kwargs参见get_value方法说明。

案例代码:

# 导入scrapy
import scrapy
# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 导入选择器
from scrapy.selector import Selector
# 定义Book类
class Book(scrapy.Item):
    # 定义name数据项,存储图书名称
    name = scrapy.Field()
    # 定义author数据项,存储图书作者
    author = scrapy.Field()
    # 定义brief数据项,存储图书简介
    brief = scrapy.Field()
    # 定义price数据项,存储图书价格
    price = scrapy.Field()
 
# 定义HTML数据
html = "<html><body><img src=\"002.png\"></body></html>"
# 定义处理函数
# 函数识别图片格式
def process_img(v):
    # 获取图片的扩展名
    extension = v[0].split(".")[1]
    if extension.lower() == "jpg":
        v[0] = "JPG图片:" + v[0]
    if extension.lower() == "png":
        v[0] = "PNG图片:" + v[0]
    if extension.lower() == "bmp":
        v[0] = "BMP图片:" + v[0]
    return v
# 实例化Selector对象
selector = Selector(text=html)
# 实例化ItemLoader类
loader = ItemLoader(item = Book(),selector=selector)
# 定义选取数据的xpath表达式
xpath = "//img"
# 调用get_xpath方法清洗数据
ret = loader.get_xpath(xpath,process_img,re="img.*src=\"(.+?\.[a-z]+)\"")
print(ret)

实例化ItemLoader对象时,若传入selector或response参数,必须要提供item参数。因此案例代码定义了结构化数据Book类,实例化ItemLoader对象时,分别传入了Book类的实例对象和selector选择器的实例对象。

ItemLoader对象的get_xpath方法会先调用selector对象执行xpath表达式,从selector对象关联的HTML内容中选取数据,然后将选取的数据再通过正则表达式对数据进一步提取,最后调用process_img处理器对提取后的内容进行处理。

注释(3)

方法声明:

get_css(css,*processors, **kwargs)

该方法通过css选择器语句,从ItemLoader关联的选择器中提取字符串列表,并对提取的字符串列表进行数据处理。

参数css是符合css选择器语法的语句。

参数processors和kwargs参见get_value方法说明。

案例代码参见注释(2)。

4、Scrapy内置数据处理器

Scarpy提供了一些常用的内置数据处理器,这些内置的数据处理器可以直接应用到ItemLoader类的get_value、add_value等方法。

内置的数据处理器在itemloaders.processors模块内。

016.png

注释(1)

数据处理器声明:

Compose(*functions, **default_loader_context)

数据处理器通过多个回调函数,依次对传入的数据进行处理。回调函数由参数functions传入,回调函数传入的顺序决定了函数的调用顺序。处理器处理的数据来自于数据数据器的调用者。

例如:

loader = ItemLoader()
value = "<img src=\"image3_thumb.jpg\">"
loader.get_value(value, Compose (process_img,upper),re=” img”)

get_value()方法调用了Compose处理器,Compose处理器的输入数据是经过正则表达式re匹配后的value数据。

Compose处理器顺序传入了两个回调函数process_img和upper,process_img回调函数首先被调用,对value数据进行处理,然后将处理后的数据再传递给upper回调函数进行处理,最后Compose处理器返回处理后的数据。

注释(2)

数据处理器声明:

Join(separator=' ')

若传入的数据是序列数据,该处理器会使用给定的分隔符拼接序列数据中的数据项。

案例代码:

# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 导入要使用的处理器
from scrapy.loader.processors import Compose,Join
# 实例化ItemLoader类
loader = ItemLoader()
# 定义待处理的value数据
value = "<img src=\"image0_thumb.jpg\">"
# 定义处理函数
# 函数识别图片格式
def process_img(v):
    # 获取图片的扩展名
    extension = v[0].split(".")[1]
    if extension.lower() == "jpg":
        v[0] = "JPG图片:" + v[0]
    if extension.lower() == "png":
        v[0] = "PNG图片:" + v[0]
    if extension.lower() == "bmp":
        v[0] = "BMP图片:" + v[0]
    return list(v[0])
ret = loader.get_value(value,Compose(process_img,Join("|")),re="img.*src=\"(.+?\.[a-z]+)\"")
print(ret)

案例代码的get_value()方法调用了Compose数据处理器,Compose数据处理器传入了两个回调函数process_img和Join,process_img是自定义的函数,Join是Scrapy内置的数据处理器。

get_value()方法会先调用正则表达式对value数据进行匹配,正则表达式以列表方式返回匹配后的数据,然后get_value()方法再调用process_img()函数对列表数据进行处理,并返回一个列表对象,最后get_value()方法再调用Join处理器对process_img()函数返回的列表对象进行处理。

程序执行结果如下所示:

J|P|G|图|片|:|i|m|a|g|e|0|_|t|h|u|m|b|.|j|p|g

注释(3)

数据处理器声明:

MapCompose(*functions, **default_loader_context)

类似于Compose处理器,可以传入多个回调函数。不同之处是MapCompose用来处理序列对象,即对序列对象的每个元素进行处理,并形成新的序列对象,再传递给后面的函数,数据被函数依次处理,最后返回处理后的数据

案例代码:

# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 导入要使用的处理器
from scrapy.loader.processors import MapCompose,Join
# 实例化ItemLoader类
loader = ItemLoader()
# 定义待处理的value数据
value = ["image0.jpg","image1.png","image2.bmp"]
# 定义处理函数
# 函数识别图片格式
def process_img(v):
    # 获取图片的扩展名
    extension = v.split(".")[1]
    if extension.lower() == "jpg":
         return "JPG图片:" + v
    if extension.lower() == "png":
         return "PNG图片:" + v
    if extension.lower() == "bmp":
         return "BMP图片:" + v
    return v
ret = loader.get_value(value,MapCompose(process_img,Join("_")))
print(ret)

案例代码的value是列表对象,MapCompose处理器会将value的每个元素分别交给process_img和Join进行处理,最后返回处理后的列表对象。

5、给每个Item数据项添加处理器

若需要为每个Item数据项添加处理器,可以使用ItemLoader为每个Item数据项提供的输入处理器和输出处理器。

输入处理器在Item数据项接收到由add_value()、add_xpath()等方法提取的数据后被调用,对提取的数据做输入前处理。此时提取的数据被保存在ItemLoader实例对象中,并没有真正存储到Item的数据项内,因此称为输入处理器。

当程序调用ItemLoader实例对象的load()方法时,输出处理器会被调用,对先前提取的数据进行输出处理,最后将处理的数据填充到Item数据项。

017.png

输入处理器和输出处理可以是自定义函数,也可以是Scapry内置的数据处理器。若要给Item数据项添加输入处理器和输出处理器,可以在定义Item数据项时添加。

Item数据项添加输入和输出处理器有两种方式:一种方式是编写一个ItemLoader类的子类,在子类为Item数据项添加输入和输出处理器;一种方式是在定义Item数据项时,为每个数据项添加输入和输出处理器。

在ItemLoader类的子类内添加输入和输出处理器

案例代码:

# 导入默认的处理器
from scrapy.loader.processors import TakeFirst, MapCompose, Join
# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 定义BookLoader类,用于对数据的清洗
# BookLoader类继承ItemLoader类
class BookLoader(ItemLoader):
    # 设置默认输出处理器
    '''
    属性default_output_processor是默认的输出处理器
    属性default_input_processor是默认的输入处理器
    若设置了默认的输出处理器,当Item数据项没有设置对应
    的处理器时,ItemLoader会使用默认的处理器
    '''
    default_output_processor = TakeFirst()
 
    # 定义Item数据项name的输入处理器
    # 在Item数据项名称后面添加_in
    name_in =  Identity()
    # 定义Item数据项name的输出处理器
    # 在Item数据项名称后面添加_out
    name_out = Join()
 
    # 定义Item数据项author的输入处理器
    # 在Item数据项名称后面添加_in
    # lstrip移除字符串的前置空格
    author_in = MapCompose(str.lstrip(' '))

案例代码使用的Item数据项参见前面的Book类。

定义Item数据项时添加输入和输出处理器

案例代码:

# 导入scrapy类
import scrapy
# 导入默认的处理器
from scrapy.loader.processors import TakeFirst, MapCompose, Join
 
# 定义处理器,过滤不是数字的字符串
def filter_price(value):
    if value.isdigit():
        return value
 
# Book类继承scrapy.Item类
class Book(scrapy.Item):
    # 定义name数据项,存储图书名称
    name = scrapy.Field(
            # 定义输入处理器,移除字符串左侧和右侧的空格
            input_processor=MapCompose(str.lstrip(' '),str.rstrip(' ')),
            # 定义输出处理器,用默认的空格拼接字符串
            output_processor=Join(),
        )
    # 定义author数据项,存储图书作者
    author = scrapy.Field(
            # 定义输入处理器,接收第一个非空的值
            input_processor=MapCompose(TakeFirst),
            # 定义输出处理器,用默认的空格拼接字符串
            output_processor=Join(),
        )
    # 定义brief数据项,存储图书简介
    brief = scrapy.Field()
    # 定义price数据项,存储图书价格
    price = scrapy.Field(
            # 过滤不是数字的字符串
            input_processor=MapCompose(filter_price),
            # 定义输出处理器,用默认的空格拼接字符串
            output_processor=Join(),
        )


代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论