scrapy将一个页面上相同类型的元素(如评论)逐条保存

    最近在学scrapy爬虫,在我爬取豆瓣的评论用来练手时,出现了一个困扰我很久的问题,解决之后,特来分享。
    我的目标是爬取每条评论的id、发送时间、评价星级、评论内容等等信息。开始时觉得很简单,事实上也确实不难,只需要用xpath取出页面上每条评论所在的元素,然后遍历取出的所有元素,将我所需要的信息一一保存到item里就行了。
item
代码如下(错误的代码,错误位置请见后文):

def parse(self, response):
    results = response.xpath("//*[@id='comments']/div[@class='comment-item']")
    for result in results:
        item_loader = ItemLoader(item=DoubanItem(), selector=result)
        item_loader.add_xpath("name", "//span[@class='comment-info']/a[1]/text()")
        item_loader.add_xpath("votes", "//span[@class='comment-vote']/span[@class='votes']/text()")
        item_loader.add_xpath("watched", "//span[@class='comment-info']/span[1]/text()")
        item_loader.add_xpath("star", "//span[contains(@class, 'star')]/@class",
                              MapCompose(lambda i: int(re.search(r"\d+", i).group(0))/10)
                              )
        item_loader.add_xpath("time", "//span[contains(@class,'comment-time ')]/@title")
        item_loader.add_xpath("comment", "//span[@class='short']/text()")
        yield item_loader.load_item()
    next_page = response.xpath("//a[@class='next']/@href").extract_first()
    if next_page:
        yield Request(
            url=urljoin(response.url, next_page),
            meta={"cookiejar": 1},
            callback=self.parse
        )

    这段代码运行的结果如下(为了看起来方便,我只爬取一页的内容):
错误的结果
    得到的结果完全不是我想要的,id、评分等信息完全没有分开,我无法把这些信息一一对应起来。而且这样的结果,与直接从response中提取没有分别。唯一有区别的,也许就是它输出了二十遍相同的结果(豆瓣一页有20条评论,所以循环了20次)
    看到这个20,我想到也许是response.xpath()出了问题,没有提取到每条评论的元素,而是返回的整个网页。于是我把提取出来的结果写入html文件(这是一个寻找问题的常见办法):

def parse(self, response):
    results = response.xpath("//*[@id='comments']/div[@class='comment-item']")
    for result in results:
    	with open("comment.html", "a", encoding="utf-8") as f:
    		f.write(str(result.extract()))

    但我发现我的想法是错的,因为写入的文件中只出现了20个评论,而不是我预想的400个。只能去找其他的问题了。

    经过一系列的折腾之后,我总算定位了错误所在,那就是add_xpath()里的表达式,少了一个’.’,它在xpath中的意思是选中当前节点

# 错误的写法
item_loader.add_xpath("name", "//span[@class='comment-info']/a[1]/text()")
# 正确的写法
item_loader.add_xpath("name", ".//span[@class='comment-info']/a[1]/text()")

    得到的结果就是我想要的了:
正确的结果

    至于为什么要这样写,在我阅读源码后,个人猜测是:response.xpath()所返回的Selector选择器里,依然包含了整个网页的内容,只不过,还有一个属性是一个xpath表达式,这个xpath表达式就代表了我们所选取的部分。当需要获取Selector选择器所选取的内容时,先用这个xpath表达式解析一遍,然后再输出解析后的结果,这就造成了一个Selector选择器只保存你所选取的网页片段的假象。而如果再用xpath表达式从Selctor选择器中提取内容时,它会把自身的xpath属性与我传入的xpath表达式拼接在一起,然后返回新的Selector选择器。所以要在传入的xpath表达式前加上一个’.’,表示从当前节点选取,不然就又会变成从整个网页选取。而我试验的结果也证明了这一点:

def parse(self, response):
    results = response.xpath("//*[@id='comments']/div[@class='comment-item']")
    for result in results:
    	# 因为这里写文件的模式是w,所以就相当于只写一个result的内容
        with open("comment.html", "w", encoding="utf-8") as f:
            f.write(result.xpath("//html").extract())

    结果为:
在这里插入图片描述
    把整个网页都打印出来了,而如果写成".//html",则什么也不会显示。这就证实了我的猜测。所以一定要记住,当用xpath从Selector选择器中提取内容时,一定要在前面加上’.’,不然就会变成从整个网页提取内容,产生意料之外的结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值