scrapy 爬虫仅爬取 start_requests 中第一个 url 的根本原因通常是 python 列表语法错误(缺少逗号导致字符串隐式拼接),本文将详解该陷阱、修复方法及最佳实践。
scrapy 爬虫仅爬取 start_requests 中第一个 url 的根本原因通常是 python 列表语法错误(缺少逗号导致字符串隐式拼接),本文将详解该陷阱、修复方法及最佳实践。
在 Scrapy 中,若希望并发爬取多个起始 URL(如罗马、米兰、贝加莫三地餐厅列表页),必须确保 start_requests() 方法中正确生成并 yield 每个请求。而原代码中存在一个极易被忽视但后果严重的语法错误:
urls = [
'https://www.tripadvisor.it/Restaurants-g187791-Rome_Lazio.html'
'https://www.tripadvisor.it/Restaurants-g187849-Milan_Lombardy.html'
'https://www.tripadvisor.it/Restaurants-g187830-Bergamo_Province_of_Bergamo_Lombardy.html'
]
⚠️ 问题本质:Python 允许相邻字符串字面量自动拼接(string literal concatenation)。上述写法等价于:
urls = [
'https://www.tripadvisor.it/Restaurants-g187791-Rome_Lazio.htmlhttps://www.tripadvisor.it/Restaurants-g187849-Milan_Lombardy.htmlhttps://www.tripadvisor.it/Restaurants-g187830-Bergamo_Province_of_Bergamo_Lombardy.html'
]
即整个列表实际只包含一个超长错误 URL,Scrapy 自然只发起一次请求——表面看是“只爬第一个”,实则是“根本没生成其余请求”。
✅ 正确写法(关键:每行末尾加逗号):
def start_requests(self):
urls = [
'https://www.tripadvisor.it/Restaurants-g187791-Rome_Lazio.html',
'https://www.tripadvisor.it/Restaurants-g187849-Milan_Lombardy.html',
'https://www.tripadvisor.it/Restaurants-g187830-Bergamo_Province_of_Bergamo_Lombardy.html',
]
for url in urls:
yield scrapy.Request(url, callback=self.parse_restaurants)
此外,为提升稳定性和反爬适应性,建议补充以下优化:
- 设置 allowed_domains:避免跨域请求被自动过滤;
- 添加请求头(headers):模拟真实浏览器行为,降低被封风险;
- 使用 .getall() 替代 .extract():Scrapy 2.0+ 推荐方法,更简洁安全;
- 移除冗余去重逻辑:Scrapy 默认启用 DUPEFILTER_CLASS,list(set(...)) 不仅低效,还可能因顺序丢失影响可重现性。
完整修复后的 start_requests 示例:
class ReviewSpider2(scrapy.Spider):
name = 'reviews2'
allowed_domains = ['tripadvisor.it']
custom_settings = {
'DOWNLOAD_DELAY': 1, # 遵守 robots.txt,避免请求过频
'RANDOMIZE_DOWNLOAD_DELAY': True,
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
}
def start_requests(self):
urls = [
'https://www.tripadvisor.it/Restaurants-g187791-Rome_Lazio.html',
'https://www.tripadvisor.it/Restaurants-g187849-Milan_Lombardy.html',
'https://www.tripadvisor.it/Restaurants-g187830-Bergamo_Province_of_Bergamo_Lombardy.html',
]
for url in urls:
yield scrapy.Request(
url=url,
callback=self.parse_restaurants,
headers=self.headers,
# 可选:添加 errback 处理请求失败
# errback=self.handle_failure
)
def parse_restaurants(self, response):
# 使用 getall() 获取所有匹配的 href,无需手动去重
restaurant_paths = response.xpath(
"//div[contains(@data-test,'_list_item')]//a[starts-with(@href,'/Restaurant_Review')]/@href"
).getall()
for path in restaurant_paths:
absolute_url = response.urljoin(path) # 更健壮的 URL 拼接方式
yield response.follow(absolute_url, callback=self.parse_restaurant)
? 注意事项总结:
- 始终检查列表/元组字面量中的逗号,尤其在多行定义时;
- 使用 response.urljoin() 替代硬编码域名拼接,提高可移植性;
- 开发阶段可在 yield 前加入 print() 或日志输出,验证 URL 是否按预期生成;
- 若仍仅执行单个请求,请用 scrapy crawl reviews2 -s LOG_LEVEL=DEBUG 查看 DEBUG 日志,确认 Crawled (200) 响应是否覆盖全部起始页。
遵循以上修正与规范,Scrapy 将真正并发调度全部起始 URL,实现多城市餐厅数据的高效、可靠采集。










