数据整理技巧笔记二

非结构化数据整理

Posted by HugoNgai on April 15, 2020

前言

最近在抓某日本电商网站的数据时,碰到了一个很恶心的情况;网页html通过后端渲染,并且没有id,class等可用的属性选择特定标签,页面的结构也不固定。这种情况在爬虫解析特定字段的时候就很头疼,没法通过xpath来选择所需要的字段,这里记录一下我针对这种情况的一种处理方式。

先看实际的例子:

# info_html
<div id="prd-page">
    <a href="https://www.rakuten.ne.jp/gold/kenkocom/">お店TOP</a><a href="https://search.rakuten.co.jp/search/inshop-mall/%E6%97%A5%E7%94%A8%E5%93%81/-/s.1-sid.193677-st.A/">日用品</a><a href="https://search.rakuten.co.jp/search/inshop-mall/%E3%82%AA%E3%83%BC%E3%83%A9%E3%83%AB%E3%82%B1%E3%82%A2/-/s.1-sid.193677-st.A/">オーラルケア</a><a href="https://search.rakuten.co.jp/search/inshop-mall/%E6%AD%AF%E3%83%96%E3%83%A9%E3%82%B7%28%E3%83%8F%E3%83%96%E3%83%A9%E3%82%B7%29/-/s.1-sid.193677-st.A/">歯ブラシ(ハブラシ)</a><a href="https://search.rakuten.co.jp/search/inshop-mall/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%AF%E3%83%88%E6%AD%AF%E3%83%96%E3%83%A9%E3%82%B7/-/s.1-sid.193677-st.A/">コンパクト歯ブラシ</a>>ガム(G・U・M)
    デンタルブラシ超コンパクトヘッドやわらかめ #166 (2本セット)<br />
    <br />
    <img src="https://image.rakuten.co.jp/kenkocom/cabinet/z104/z35104.jpg" width="100%" /><br />
    <img src="https://image.rakuten.co.jp/kenkocom/cabinet/z104/z35104-2.jpg" width="100%" /><br />
    <img src="https://image.rakuten.co.jp/kenkocom/cabinet/z104/z35104-3.jpg" width="100%" /><br />
    <br />
    【ガム(G・U・M) デンタルブラシ超コンパクトヘッドやわらかめ #166の商品詳細】<br />
    ●歯周病の原因菌は歯とハグキの境目の歯垢に潜んでいます。<br />
    ・細くしなやかな毛先に加工されているので、ハグキにやさしくあたり、歯とハグキの境目にしっかり届いて歯垢を除去します。<br />
    1.毛先ウルトラテーパード加工<br />
    2.特殊サテナイズド処理<br />
    <br />
    【柄の材質】<br />
    飽和ポリエステル樹脂<br />
    <br />
    【毛の材質】<br />
    飽和ポリエステル樹脂<br />
    <br />
    【毛のかたさ】<br />
    やわらかめ<br />
    <br />
    【耐熱温度】<br />
    60℃<br />
    <br />
    【歯ブラシの当て方】<br />
    毛先を歯とハグキの境目に当て、軽い力で小刻みに磨きます。<br />
    <br />
    【ご注意】<br />
    ・使用後は流水で歯みがきを充分洗い落とし、水を切って風通しの良いところにおいてください。<br />
    ・変色、変形の恐れがありますので塩素系殺菌剤、漂白剤や熱湯にはつけないでください。<br />
    ・毛先がひらいたら、とりかえましょう。<br />
    <br />
    【ブランド】<br />
    <a href="https://search.rakuten.co.jp/search/inshop-mall/%E3%82%AC%E3%83%A0%28G%E3%83%BBU%E3%83%BBM%29/-/s.1-sid.193677-st.A/">ガム(G・U・M)</a><br />
    <br />
    【発売元、製造元、輸入元又は販売元】<br />
    サンスター<br />
    <br />
    <font color="red">
        商品画像とデザイン・カラーが異なる場合がございます。<br />
        アソートのため、色の指定はできません。予めご了承下さい。<br />
        <br />
        ※説明文は単品の内容です。
    </font>
    <br />
    <br />
    <font color="red">リニューアルに伴い、パッケージ・内容等予告なく変更する場合がございます。予めご了承ください。</font><br />
    <br />
    (GUM)<br />
    <br />
    ・単品JAN:4901616212978<br />
    <br />
    サンスター<br />
    569-1195 大阪府高槻市朝日町3-1<br />
    0120-008241<br />
    <br />
    広告文責:楽天株式会社<br />
    電話:050-5577-5042<br />
    <br />
    [歯ブラシ・電動歯ブラシ/ブランド:ガム(G・U・M)/]
</div>

从html代码可以看出来,处理很简陋,直接把代码作为接口字段返回,在页面实现渲染。前4个a标签是category内容,后半段还有一个a标签,是品牌内容;如果我们想要分别提取category和brand的数据,单纯通过xpath选取a标签很难处理(因为观察发现不同商品的页面结构不一致,有些并没有brand这个属性);中间部分的各种属性及其对应的描述也很难通过xpath提取,很难写出通用的提取代码。

正文

最终我决定使用字典(dict)数据结构来对内容进行初步提取清洗,形成结构化数据;再用代码提取所需的数据字段。

  • Step One

    观察一下这part html代码,可以发现一个简单的规律:每个属性的title都用【】括起来,然后两个title之间的文本为对应的描述。所以切入点在这个括号。

    首先我们把这部分html当作纯文本来处理,把所有的标签去除。

from lxml import etree
info_html = etree.HTML(raw_data)
text = info_html.xpath('//text()')
  • Step Two

    把文本全部提取出来以后,开始根据【】把所有的title提取出来,把title作为字典的key,对应的描述作为value。提取方式很简单,遍历文本数组text,判断是否包含【】即可,这里还有一个额外的操作,就是记录这个title的数组下标,方便我们后面进行切分的时候直接选出title。根据筛选出来的下标数组,两个下标之间的数据即为该下标对应的内容。

index_list = [text.index(i) for i in list(filter(lambda val: '【' in val and '】' in val, text))]	// 记录title下标
info_dict = dict()
for i in range(len(index_list)):
    index_ = index_list[i]
    next_index = i < len(index_list) - 1 and index_list[i + 1] or None	# 取下一个下标,先做边界判断,避免数组越界
    key = text[index_]	# title,作为字典中的key
    value = next_index and text[index_ + 1:next_index] or text[index_ + 1:]	# 两个下标之间的数据为对应的内容,作为字典的value
    info_dict.update({key: ' '.join(value)})
cat_info = ''.join(text[0:index_list[0]])   # 提取category信息
  • Step Three

    通过上面的代码处理完成以后,category信息结构化为数组,各种属性及对应的描述通过dict的{key: value}形式清洗完成,需要提取的时候只需要通过info[title]就可以提取出对应的描述内容来了。

思考和总结

​ 碰到这种很恶心的情况,通过常规思路xpath来处理的话,会很麻烦,而且也很难实现代码复用;转化一种思路,找找规律,会发现先对数据进行清洗,然后再提取反而更容易处理,而且代码也可以复用起来。上面这种处理方式,牺牲了一点额外的空间,但是换回了时间;数组的遍历O(n),字典的查找O(1),时间复杂度为O(n),这个结果是可以接受的。如果通过写正则来提取,感觉是个噩梦,而且爬虫代码的健壮性和通用性将会变得十分糟糕。