You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy爬虫重复抓取且遗漏唯一条目,每次运行结果不同

Scrapy + Splash 爬取Tripadvisor出现重复条目和缺失数据的解决方案

首先,我帮你梳理下核心问题的根源,然后一步步给出修复方案:

1. 复用Item对象导致数据覆盖(最关键问题)

你在parse方法里把items = HotelinfoItem()放在了循环外面,这会导致所有酒店条目共用同一个Item实例。Scrapy是异步框架,当你yield items时,后续的循环迭代会修改这个Item的内容,最终多条数据指向同一个对象,就会出现重复或者数据混乱的情况——这也是为什么每次重复的条目都不一样的原因。

修复方法:把Item的初始化移到循环内部,确保每个酒店都有独立的Item对象:

def parse(self, response):
    all_single_entries = response.css("div.listItem")
    for entry in all_single_entries:
        items = HotelinfoItem()  # 移到循环里面!
        # 后续的赋值逻辑保持不变,继续处理当前酒店的数据
        ...
        yield items

2. 未启用去重Pipeline,重复条目无法被过滤

你已经写了DuplicatesPipeline,但在settings.py的ITEM_PIPELINES里完全没配置它!这就导致即使抓到重复条目,也不会被丢弃,反而占用了总条数的名额,导致其他正常酒店条目被挤掉,出现“部分酒店缺失”的情况。

修复步骤:

  • 先修改Item的字段赋值逻辑:你现在把所有字段都存成了列表(比如items["hotel_ids"] = [str(...)]),但每个酒店对应一个值,列表是不可哈希的,没法直接放到集合里去重。所以要把字段改成单个字符串:
# 提取酒店名称:用extract_first()替代extract()+字符串分割,更稳定
hotel_name = entry.css(".listing_title [target=_blank]::text").extract_first(default="NA").strip()
items["hotel_names"] = hotel_name

# 提取酒店链接
hotel_link = entry.css(".listing_title a::attr(href)").extract_first(default="")
items["hotel_links"] = f"https://www.tripadvisor.com{hotel_link}" if hotel_link else "NA"

# 提取酒店ID
hotel_id = entry.css(".listing_title a::attr(id)").extract_first(default="")
items["hotel_ids"] = hotel_id.split("_")[1] if "_" in hotel_id else "NA"

# 提取价格
hotel_price = entry.css(".premium_offer_container div::attr(data-pernight)").extract_first(default="NA")
items["hotel_displayed_price"] = hotel_price

# 提取酒店类型
hotel_type = entry.css(".mb10 .label::text").extract_first(default="Hotel").strip()
items["hotel_type"] = hotel_type
  • 然后在settings.py里启用去重Pipeline,注意优先级要比存储Pipeline高(数字越小,优先级越高):
ITEM_PIPELINES = {
    'Hotelinfo.pipelines.DuplicatesPipeline': 100,  # 先执行去重
    'Hotelinfo.pipelines.HotelinfoPipeline': 300,   # 再执行存储
}
  • 最后更新DuplicatesPipeline的逻辑,确保判断的是单个ID值:
class DuplicatesPipeline:
    def __init__(self):
        self.ids_se跳过 io Respectenter_lookingexpand留下Over_临时 elabor streaming广大 Official定想,哦不,初始化去重集合:
        self.ids_seen = set()

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        hotel_id = adapter['hotel_ids']
        if hotel_id in self.ids_seen:
            raise DropItem(f"Duplicate item found: {item['hotel_names']} (ID: {hotel_id})")
        else:
            self.ids_seen.add(hotel_id)
            return item

3. 分页请求的Splash配置有问题

你在初始请求里用了wait=10,但分页请求里却用了wait=5,而且还用了response.follow来发起分页请求——response.follow是针对普通HTTP请求的,没法正确触发Splash的页面渲染,这会导致分页页面加载不充分,出现条目重复或者漏抓。

修复方法:分页请求改用SplashRequest,统一等待时间为10秒,甚至可以添加JS等待逻辑确保页面加载完成:

next_page = response.css("a.next::attr(href)").get()
if next_page is not None:
    next_page_url = f"https://www.tripadvisor.com{next_page.split('#')[0]}"
    yield SplashRequest(
        url=next_page_url,
        callback=self.parse,
        args={
            "wait": 10,
            # 可选:等待列表元素加载完成再返回,比固定等待更可靠
            "js_source": """
                function main(splash) {
                    return splash.wait_for_element('div.listItem');
                }
            """
        }
    )

4. 数据库层面添加双重保障

你的HotelinfoPipeline每次运行都会删除原表重建,而且没有主键约束。建议给hotel_ids添加主键,这样即使Pipeline去重失效,数据库也会拒绝插入重复ID的数据:

def create_table(self):
    self.curr.execute("""DROP TABLE IF EXISTS hotel_links_with_prices_tb""")
    self.curr.execute("""
    CREATE TABLE hotel_links_with_prices_tb (
        hotel_ids VARCHAR(50) PRIMARY KEY,  # 用hotel_id作为主键,避免重复
        hotel_names TEXT NOT NULL,
        hotel_links TEXT NOT NULL,
        hotel_displayed_price TEXT,
        hotel_type TEXT NOT NULL
    )""")

最后建议

  • 开启Scrapy的日志(在settings.py里设置LOG_LEVEL = 'INFO'),这样可以看到DuplicatesPipeline是否在正常丢弃重复条目,方便排查问题。
  • 可以在parse方法里打印每个条目的ID,观察是否有重复提取的情况,确认数据提取逻辑是否正确。

按照这些步骤修改后,应该就能解决重复条目和部分酒店缺失的问题了!

内容的提问来源于stack exchange,提问作者KindLundt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 17:32:34