datawhale组织本次学习内容bs4、xpath和正则表达式
目录
-
1.xpath学习
-
学习xpath,使用lxml+xpath提取内容。
-
使用xpath提取丁香园论坛的回复内容。
1.1知识要点:
XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历,XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
Xpath常用的路径表达式
- XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
- 在XPath中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。
- XML文档是被作为节点树来对待的。
XPath使用路径表达式在XML文档中选取节点。节点是通过沿着路径选取的。下面列出了最常用的路径表达式:
- nodename 选取此节点的所有子节点。
- / 从根节点选取。
- // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
- . 选取当前节点。
- .. 选取当前节点的父节点。
- @ 选取属性。
- /text() 提取标签下面的文本内容
参考文档 https://www.cnblogs.com/gaojun/archive/2012/08/11/2633908.html,摘取本次用到的知识点罗列如下:
<?xml version="1.0"?>
<root>
<child attr="attr" />
<child>
<a><desc /></a>
</child>
</root>
{针对上面的XML文档的XPath结果,当前节点为document
/root {选取root
root {选取root
child {空,因为child不是document的子元素
//child {选取两个child元素,//表示后代
//@attr {选取attr属性节点
/root/child//desc {返回child的后代元素desc
谓语(Predicates)
谓语用于在查找节点时提供更详尽的信息,谓语被嵌在方括号中。下面是一些带有谓语的XPath表达式:
/root/child[3] {选取root元素的第三个child子元素,注意,这和数组下标不一样,从1开始计数
//child[@attr] {选取所有具有属性attr的child元素
//child[@attr="val"]/desc {选取所有属性attr的值为val的child元素的子元素desc
//child[desc] {选取所有的有desc子元素的child
//child[position()>3] {position()是XPath中的一个函数,表示节点的位置
//child[@attr>12] {XPath表达式还可以进行数值比较,该表达式将选取attr属性值大于12的child元素
//child[last()] {last()函数返回节点列表最后的位置,该表达式将选取最后一个child元素
通配符
XPath 通配符可用来选取未知的 XML 元素。
- * ,和CSS中的选择符一样,这将匹配任何元素节点
- @* ,匹配任何属性节点
- node() ,匹配任何类型的节点
/root/* {选取根元素下面的所有子元素
/root/node() {选取根元素下面的所有节点,包括文本节点
//* {选取文档中所有元素
//child[@*] {选取所有具有属性的child元素
//@* {选取所有的属性节点
组合路径
与CSS中使用逗号组合使用多个选择符一样,XPath支持一种使用"|"来组合多个路径的语法!
/root | /root/child {选取根元素root与它下面的子元素child
//child | //desc {选取所有的child元素与desc元素
XPath 运算符
下面列出了可用在 XPath 表达式中的运算符:
- | ,计算两个节点集
- + ,加法
- - ,减法
- * ,乘法
- div ,除法,因为/已经被作为路径符了,所以不能用来作为除法标识
- mod ,取余
- = ,等于
- != ,不等于
- < ,小于
- <= ,小于或等于
- > ,大于
- >= ,大于或等于
- or ,或
- and ,与
1.2 使用lxml解析
-
导入库:from lxml import etree
-
lxml将html文本转成xml对象
- tree = etree.HTML(html)
-
用户名称:tree.xpath(’//div[@class=“auth”]/a/text()’)
-
回复内容:tree.xpath(’//td[@class=“postbody”]’) 因为回复内容中有换行等标签,所以需要用string()来获取数据。
- string()的详细见链接:https://www.cnblogs.com/CYHISTW/p/12312570.html
-
Xpath中text(),string(),data()的区别如下:
- text()仅仅返回所指元素的文本内容, 该方法可以提取当前元素的信息,但是某些元素下包含很多嵌套元素。
- string()函数会得到所指元素的所有节点文本内容,这些文本讲会被拼接成一个字符串。
#string(.)方法不能直接与之前的xpath写到一起,需要在之前的对象的基础上使用 #注意xpath直接拿到的是一个列表,想要使用string(.)方法要在之前的对象加上[0]
- data()大多数时候,data()函数和string()函数通用,而且不建议经常使用data()函数,有数据表明,该函数会影响XPath的性能。
1.3 实战:爬取丁香园-用户名和回复内容
爬取思路:
- 获取url的html
- lxml解析html
- 利用Xpath表达式获取user和content
- 保存爬取的内容
from lxml import etree
import requests
url='http://www.dxy.cn/bbs/thread/626626#626626'
#1.获取url的html
req = requests.get(url)
html = req.text
#2.lxml解析html
tree = etree.HTML(html) # lxml解析html
#3.利用Xpath表达式获取user和content(完成xpath的语句)
user = tree.xpath('//div[@class="auth"]/a/text()')
print(user)
content = tree.xpath('//td[@class="postbody"]')
print(content)
#4保存爬取的内容
results = []
for i in range(0, len(user)):
# 因为回复内容中有换行等标签,所以需要用string()来获取数据
results.append(user[i].strip() + ": " + content[i].xpath('string(.)').strip())
# 打印爬取的结果
for i,result in zip(range(0, len(user)),results):
print("user"+ str(i+1) + "-" + result)
print("*"*100)
(其中,zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。)
结果展示
为清楚string(.)与text()的区别,如果内容展示也用text(),则展示效果如下,列表中出现空格等构成的元素。因此需用string()
content = tree.xpath('//td[@class="postbody"]/text()')
2.Beautiful Soup学习
2.1 Beautiful Soup库基本知识点
-
学习beautifulsoup基础知识。
-
使用beautifulsoup解析HTML页面。
- Beautiful Soup 是一个HTML/XML 的解析器,主要用于解析和提取 HTML/XML 数据。
- 它基于HTML DOM 的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
- BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
- 虽然说BeautifulSoup4 简单容易比较上手,但是匹配效率还是远远不如正则以及xpath的,一般不推荐使用,推荐正则的使用。
-
第一步:pip install beautifulsoup4 ,万事开头难,先安装 beautifulsoup4,安装成功后就完成了第一步。
-
第二步:导入from bs4 import BeautifulSoup
-
第三步:创建 Beautiful Soup对象 soup = BeautifulSoup(html,'html.parser')
2.1.1 Beautiful Soup库的基本元素
-
Beautiful Soup库的理解: Beautiful Soup库是解析、遍历、维护“标签树”的功能库,对应一个HTML/XML文档的全部内容
-
BeautifulSoup类的基本元素:
Tag 标签,最基本的信息组织单元,分别用<>和</>标明开头和结尾;
Name 标签的名字,<p>…</p>的名字是'p',格式:<tag>.name;
Attributes 标签的属性,字典形式组织,格式:<tag>.attrs;
NavigableString 标签内非属性字符串,<>…</>中字符串,格式:<tag>.string;
Comment 标签内字符串的注释部分,一种特殊的Comment类型;
2.1.2 基于bs4库的HTML内容遍历方法
HTML基本格式:<>…</>
构成了所属关系,形成了标签的树形结构
- 标签树的下行遍历
- .contents 子节点的列表,将
<tag>
所有儿子节点存入列表 - .children 子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
- .descendants 子孙节点的迭代类型,包含所有子孙节点,用于循环遍历
- .contents 子节点的列表,将
- 标签树的上行遍
- .parent 节点的父亲标签
- .parents 节点先辈标签的迭代类型,用于循环遍历先辈节点
- 标签树的平行遍历
- .next_sibling 返回按照HTML文本顺序的下一个平行节点标签
- .previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
- .next_siblings 迭代类型,返回按照HTML文本顺序的后续所有平行节点标签
- .previous_siblings 迭代类型,返回按照HTML文本顺序的前续所有平行节点标签
- 详见:https://www.cnblogs.com/mengxiaoleng/p/11585754.html#_label0
2.1.3 基于bs4库的HTML内容的查找方法
- <>.find_all(name, attrs, recursive, string, **kwargs)
- 参数:
- ∙ name : 对标签名称的检索字符串
- ∙ attrs: 对标签属性值的检索字符串,可标注属性检索
- ∙ recursive: 是否对子孙全部检索,默认True
- ∙ string: <>…</>中字符串区域的检索字符串
- 简写:
<tag>
(..) 等价于<tag>
.find_all(..)- soup(..) 等价于 soup.find_all(..)
- 扩展方法:
- <>.find() 搜索且只返回一个结果,同.find_all()参数
- <>.find_parents() 在先辈节点中搜索,返回列表类型,同.find_all()参数
- <>.find_parent() 在先辈节点中返回一个结果,同.find()参数
- <>.find_next_siblings() 在后续平行节点中搜索,返回列表类型,同.find_all()参数
- <>.find_next_sibling() 在后续平行节点中返回一个结果,同.find()参数
- <>.find_previous_siblings() 在前序平行节点中搜索,返回列表类型,同.find_all()参数
- <>.find_previous_sibling() 在前序平行节点中返回一个结果,同.find()参数
2.2 实战:中国大学排名定向爬取
爬取思路:
- 从网络上获取大学排名网页内容
- 提取网页内容中信息到合适的数据结构(二维数组)-排名,学校名称,总分
- 利用数据结构展示并输出结果
import requests
from bs4 import BeautifulSoup
import bs4
#1. 从网络上获取大学排名网页内容
r = requests.get('http://www.zuihaodaxue.cn/zuihaodaxuepaiming2019.html')
#2. 提取网页内容中信息到合适的数据结构(二维数组)
#1)查看网页源代码,观察并定位到需要爬取内容的标签;
#2)使用bs4的查找方法提取所需信息-'排名,学校名称,总分''''
r.encoding='utf-8'
dx = r.text
soup = BeautifulSoup(dx,'html.parser')
infos=soup.find_all('tr',{'class':'alt'})
#print(infos)
#3. 利用数据结构展示并输出结果
tplt = "{0:^4}\t{1:{5}^12}\t{2:{5}^6}\t{3:^10}\t{4:^10}"
#中英文混合字符串对齐则需用中文空格chr(12288)填充
print(tplt.format("排名","学校名称", "省市", "score", "data",chr(12288)))
for i in range(0,30):
data=infos[i].find_all('td')
#print(data)
rank=data[0].get_text()
school=data[1].get_text()
prov=data[2].get_text()
score=data[3].get_text()
indexscore=data[4].get_text()
print(tplt.format(rank,school,prov,score,indexscore,chr(12288)))
输出结果如下:
排名 学校名称 省市 score data
1 清华大学 北京 94.6 100.0
2 北京大学 北京 76.5 95.2
3 浙江大学 浙江 72.9 84.2
4 上海交通大学 上海 72.1 91.1
5 复旦大学 上海 65.6 91.6
6 中国科学技术大学 安徽 60.9 91.1
7 华中科技大学 湖北 58.9 80.1
7 南京大学 江苏 58.9 86.2
9 中山大学 广东 58.2 79.7
10 哈尔滨工业大学 黑龙江 56.7 76.6
11 北京航空航天大学 北京 56.3 86.9
12 武汉大学 湖北 56.2 82.4
13 同济大学 上海 55.7 85.2
14 西安交通大学 陕西 55.0 82.0
15 四川大学 四川 54.4 74.0
16 北京理工大学 北京 54.0 79.7
17 东南大学 江苏 53.6 79.4
18 南开大学 天津 52.8 83.9
19 天津大学 天津 52.3 80.1
20 华南理工大学 广东 52.0 73.4
21 中南大学 湖南 50.3 73.2
22 北京师范大学 北京 49.7 82.7
23 山东大学 山东 49.1 71.3
23 厦门大学 福建 49.1 75.3
25 吉林大学 吉林 48.9 71.9
26 大连理工大学 辽宁 48.6 72.9
27 电子科技大学 四川 48.4 73.8
28 湖南大学 湖南 48.1 71.7
29 苏州大学 江苏 47.3 64.7
30 西北工业大学 陕西 46.7 74.0
其中中英文混合对齐解释如下:
以上图片出自北京理工 嵩天 Python网络爬虫与信息提取课程(中国大学MOOC网站)。
打印输出要注意的问题是中文字符宽度不到指定的宽度时,系统默认使用英文空格填充,导致文本无法对齐,使用中文空格填充就可以对齐了(用中文空格chr(12288)填充)。
tplt.format("排名","学校名称", "省市", "score", "data",chr(12288)),其中{1:{5}^12}中1和5代表的时format中的第几个变量(从0开始计数),对齐符号^前{5}表示填充的内容即为中文空格chr(12288)。
3 学习正则表达式
3.1 为什么使用正则表达式?
典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性,若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。
通过使用正则表达式,可以:
- 测试字符串内的模式。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。
- 替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。
- 基于模式匹配从字符串中提取子字符串。
可以查找文档内或输入域内特定的文本。
可以使用正则表达式来搜索和替换标记。
使用正则表达式的优势是什么? 简洁
- 正则表达式是用来简洁表达一组字符串的表达式
- 正则表达式是一种通用的字符串表达框架
- 正则表达式是一种针对字符串表达“简洁”和“特征”思想的工具
- 正则表达式可以用来判断某字符串的特征归属
正则表达式在文本处理中十分常用:
- 同时查找或替换一组字符串
- 匹配字符串的全部或部分(主要)
3.2 正则表达式语法
正则表达式语法由字符和操作符构成:
-
常用操作符
-
.
表示任何单个字符 -
[ ]
字符集,对单个字符给出取值范围 ,如[abc]
表示a、b、c,[a‐z]
表示a到z单个字符 -
[^ ]
非字符集,对单个字符给出排除范围 ,如[^abc]
表示非a或b或c的单个字符 -
*
前一个字符0次或无限次扩展,如abc* 表示 ab、abc、abcc、abccc等 -
+
前一个字符1次或无限次扩展 ,如abc+ 表示 abc、abcc、abccc等 -
?
前一个字符0次或1次扩展 ,如abc? 表示 ab、abc -
|
左右表达式任意一个 ,如abc|def 表示 abc、def -
{m}
扩展前一个字符m次 ,如ab{2}c表示abbc -
{m,n}
扩展前一个字符m至n次(含n) ,如ab{1,2}c表示abc、abbc -
^
匹配字符串开头 ,如^abc表示abc且在一个字符串的开头 -
$
匹配字符串结尾 ,如abc$表示abc且在一个字符串的结尾 -
( )
分组标记,内部只能使用 | 操作符 ,如(abc)表示abc,(abc|def)表示abc、def -
\d
数字,等价于[0‐9]
-
\w
单词字符,等价于[A‐Za‐z0‐9_]
-
3.3 正则表达式re库的使用
- 调用方式:import re
- re库采用raw string类型表示正则表达式,表示为:r'text',raw string是不包含对转义符再次转义的字符串;
re库的主要功能函数:
-
re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象
- re.search(pattern, string, flags=0)
-
re.match() 从一个字符串的开始位置起匹配正则表达式,返回match对象
- re.match(pattern, string, flags=0)
-
re.findall() 搜索字符串,以列表类型返回全部能匹配的子串
- re.findall(pattern, string, flags=0)
-
re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型
- re.split(pattern, string, maxsplit=0, flags=0)
-
re.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象
- re.finditer(pattern, string, flags=0)
-
re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串
-
re.sub(pattern, repl, string, count=0, flags=0)
-
flags : 正则表达式使用时的控制标记:
- re.I --> re.IGNORECASE : 忽略正则表达式的大小写,
[A‐Z]
能够匹配小写字符 - re.M --> re.MULTILINE : 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始
- re.S --> re.DOTALL : 正则表达式中的.操作符能够匹配所有字符,默认匹配除换行外的所有字符
- re.I --> re.IGNORECASE : 忽略正则表达式的大小写,
-
re库的另一种等价用法(编译)
- regex = re.compile(pattern, flags=0):将正则表达式的字符串形式编译成正则表达式对象
re 库的贪婪匹配和最小匹配
.*
Re库默认采用贪婪匹配,即输出匹配最长的子串*?
只要长度输出可能不同的,都可以通过在操作符后增加?变成最小匹配
3.4 实战:淘宝商品比价定向爬虫
- 爬取网址:https://s.taobao.com/search?q=书包&js=1&stats_click=search_radio_all%25
- 爬取思路:
- 提交商品搜索请求,循环获取页面
- 对于每个页面,提取商品名称和价格信息
- 将信息输出到屏幕上
# 导入包
import requests
import re
#1. 提交商品搜索请求,循环获取页面
def getHTMLText(url):
"""
请求获取html,(字符串)
:param url: 爬取网址
:return: 字符串
"""
try:
# 添加头信息,
kv = {
'cookie':'cna=gtvMFPLxknUCAatxi3kvVcBB; tg=0; miid=1446416976641969558; hng=CN%7Czh-CN%7CCNY%7C156; thw=cn; tracknick=%5Cu902F%5Cu74332014; _cc_=VFC%2FuZ9ajQ%3D%3D; t=e813725b132e94b357dd22c95311845b; _m_h5_tk=89be0c650e02c882de60d0e7d2c509cc_1587662839145; _m_h5_tk_enc=a2719fbae9834ded0b67f97f5233a1d0; cookie2=1e7c9645543a41f038194380e4c27070; _tb_token_=e3ee5bb753b0e; _samesite_flag_=true; sgcookie=EJqofoNOzpTNE%2F4DVABPZ; unb=2248894838; uc3=id2=UUplaX1DVmMK3Q%3D%3D&nk2=hn7nVUaF5zw%3D&lg2=URm48syIIVrSKA%3D%3D&vt3=F8dBxGR1TQvicANIjaY%3D; csg=6eec1b34; lgc=%5Cu902F%5Cu74332014; cookie17=UUplaX1DVmMK3Q%3D%3D; dnk=%5Cu902F%5Cu74332014; skt=86b6ad2d66ba3ed6; existShop=MTU4NzY1Mzg5NQ%3D%3D; uc4=nk4=0%40hLTZ4z62WQPzPJJzG9EvN8%2BVjQ%3D%3D&id4=0%40U2gvJuWG2xPP1nPxseaEKdL9pM7m; _l_g_=Ug%3D%3D; sg=48c; _nk_=%5Cu902F%5Cu74332014; cookie1=V361bKVihS9Ec%2BGSMKnl7K7Px2FZWaoLYHrpRkY1HjE%3D; tfstk=cIdPB7Z8ebhr1fGvB_CFPnxqaTl5a48k-S71Edyxf-QjvcW5bsAuvZ0kIZ7Qa5Wl.; mt=ci=102_1; v=0; enc=5vFGCcJXkzvDQzA9NXIAfAim6Iw%2BNBZQXZNDahdXYKUhtwxqMIJG%2B3RO592zrccYhNqcfmBK7Ho9eStxn7rSTw%3D%3D; uc1=cookie16=VFC%2FuZ9az08KUQ56dCrZDlbNdA%3D%3D&cookie21=U%2BGCWk%2F7p4mBoUyS4E9C&cookie15=VT5L2FSpMGV7TQ%3D%3D&existShop=false&pas=0&cookie14=UoTUPcqZfSQc0Q%3D%3D; l=eBQKPbk7qfVR6p6wBOfwFurza77tsIRAguPzaNbMiT5P_vfp5IO1WZjj9a89CnGVh6lBR3rEQAfYBeYBqIv4n5U62j-lasDmn; isg=BNfX-3iNRY_YG8Jl43zhtsOmZkshHKt-loTXnSkE9qYNWPeaMeySzo5-uvjGsIP2',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
}
r = requests.get(url, timeout=30, headers=kv)
r.raise_for_status()
#print(r.status_code)
r.encoding = r.apparent_encoding
return r.text
except:
return "爬取失败"
#2. 对于每个页面,提取商品名称和价格信息
def parsePage(glist, html):
'''
解析网页,搜索需要的信息
:param glist: 列表作为存储容器
:param html: 由getHTMLText()得到的
:return: 商品信息的列表
'''
try:
# 使用正则表达式提取信息
price_list = re.findall(r'\"view_price\"\:\"[\d\.]*\"', html)
name_list = re.findall(r'\"raw_title\"\:\".*?\"', html)
for i in range(len(price_list)):
price = eval(price_list[i].split(":")[1]) #eval()在此可以去掉""
name = eval(name_list[i].split(":")[1])
glist.append([price, name])
except:
print("解析失败")
#3. 将信息输出到屏幕上
def printGoodList(glist):
tplt = "{0:^4}\t{1:^6}\t{2:^10}"
print(tplt.format("序号", "商品价格", "商品名称"))
count = 0
for g in glist:
count = count + 1
print(tplt.format(count, g[0], g[1]))
def main():
# 根据页面url的变化寻找规律,构建爬取url
goods_name = "饭盒" # 搜索商品类型
start_url = "https://s.taobao.com/search?q=" + goods_name
info_list = []
page = 3 # 爬取页面数量
count = 0
for i in range(page):
count += 1
try:
url = start_url + "&s=" + str(44 * i) #每页商品展示为44个
html = getHTMLText(url) # 爬取url
parsePage(info_list, html) #解析HTML和爬取内容
print("\r爬取页面当前进度: {:.2f}%".format(count * 100 / page), end="") # 显示进度条
except:
continue
print('\n')
printGoodList(info_list)
main()
注:默认淘宝是未登录的,需登录账号,获取cookie/user-agent信息,替换到headers内容中。
序号 商品价格 商品名称
1 29.80 上班族玻璃饭盒微波炉专用保鲜便当女带盖碗
2 64.80 小熊电热饭盒保温可插电加热自热蒸煮热饭神器带饭锅桶上班族便携
3 229.00 生活元素电热饭盒保温可插电加热自热蒸煮饭带饭热饭菜神器上班族
4 79.00 富光多层保温饭盒大容量不锈钢超长保温桶便当盒便携学生上班族
5 49.00 富光保温饭盒桶焖烧杯罐闷烧杯壶女学生上班族焖粥神器1人便携当
6 95.00 304不锈钢保温饭盒可微波炉加热上班族便当盒桶1人便携带多层分隔
7 89.83 苏泊尔保温饭盒超长保温多层不锈钢1人便携学生上班族家用保温桶
8 49.00 304不锈钢多层保温饭盒分隔型上班族便当餐盒携学生日式桶可爱1人
9 39.00 304不锈钢保温饭盒小学生儿童分隔型上班族便携便当盒餐盒套装1人
10 38.80 304不锈钢保温饭盒1人便携分隔可带汤学生上班族便当餐盘餐盒套装
11 29.80 创得上班族玻璃饭盒可微波炉加热专用保鲜分隔型学生便当带盖餐碗
12 39.00 304不锈钢保温饭盒上班族隔层学生便当餐盒分隔型便携桶超长多层
13 29.00 304不锈钢饭盒儿童防烫小学生饭缸上班族成人打便当餐盒食堂带饭
14 28.00 304不锈钢加热饭盒上班族注水多层保温饭盒便携微波炉学生分隔型
15 12.90 304不锈钢饭盒便当盒保温学生食堂分格便携分隔型上班族餐盒套装
16 19.80 玻璃饭盒上班族可微波炉加热专用碗学生保鲜盒分隔型便当盒餐盒格
17 149.73 苏泊尔保温饭盒不锈钢保温桶家用成人多层1人便携上班族便当盒
18 79.00 日本泰福高304不锈钢饭盒学生分格餐盒上班族分隔餐盘便携便当盒
19 34.90 便携手提304不锈钢便当盒12小时1人学生成人双层提锅女保温桶饭盒
20 12.90 简约不锈钢饭盒保温分格1成人可爱便当盒学生2韩国3多层4带盖餐盒