前言
最近在抓某日本电商网站的数据时,碰到了一个很恶心的情况;网页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),这个结果是可以接受的。如果通过写正则来提取,感觉是个噩梦,而且爬虫代码的健壮性和通用性将会变得十分糟糕。