Scrapy多URL爬取CSV数据被覆盖问题求助
问题:Scrapy多URL爬取时CSV数据被覆盖
我写了个简单的Scrapy脚本,用来从jobs2careers爬取招聘信息,用Item组件导出到CSV。但碰到个头疼的问题:当配置多个起始URL时,导出的CSV数据会被覆盖。我试过用openpyxl这类库,怀疑是不是多Spider运行导致的?下面是我的脚本代码:
import scrapy from scrapy.selector import Selector from ..items import QuotetutorialItem class QuotesSpider(scrapy.Spider): name = 'quotes' n = 1 start_urls = ['https://www.jobs2careers.com/results3.php?q=Fashion&l=Miami%2C+FL&s=00'] def parse(self, response): items = QuotetutorialItem() s = Selector(response) quote = s.xpath("//div[@id='jobs_container']/div") for q in quote: url = response.url industry = q.xpath("//input[@id='search_q']/@value").get() state = q.xpath("//*[@id='results_controller']/div[1]/div[1]/div/div/div/div/a[3]/text()").get() company_name = q.xpath(".//div[@class='companyname']/span[@class='company']/text()").get() job_title = q.xpath(".//div[@class='title title1 hidden-xs']/text()").get() items['url'] = url items['industry'] = industry items['state'] = state items['company_name'] = company_name items['job_title'] = job_title yield items num = int(response.xpath("//h1[@class='result2']//text()").get().split("\n")[0].replace(',', '')) if num > 1000: num = 1000 total = int(num) // 10 + 1 np = response.url np = np[np.rfind('=') + 1:] next_page = response.url.replace(np, str((self.n * 10))) if self.n < total: self.n += 1 yield response.follow(next_page,callback = self.parse)
解决方案
先给你拆解下问题根源,再一步步解决:
1. CSV覆盖的核心原因:默认导出是覆盖模式
Scrapy自带的CSV导出器,默认是覆盖写入文件的——不管你是单个Spider加多个起始URL,还是多次运行Spider,都会把旧文件清空再写新数据。这是最主要的问题,咱们先解决这个:
方法一:改成追加模式(推荐)
在项目的settings.py里添加/修改FEEDS配置,把overwrite设为False:
FEEDS = { 'jobs_data.csv': { 'format': 'csv', 'encoding': 'utf-8', 'overwrite': False, # 关键:设为False就会追加,不会覆盖旧数据 'store_empty': False, } }
这样不管你加多少个起始URL,或者多次运行爬虫,新数据都会追加到CSV末尾,不会丢旧数据。
方法二:生成动态文件名(避免同文件追加)
如果你不想把所有数据堆在一个文件里,想每次运行生成新文件,可以用时间戳命名:
from datetime import datetime FEEDS = { f'jobs_data_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv': { 'format': 'csv', 'encoding': 'utf-8', 'overwrite': True, } }
每次运行都会生成一个带时间戳的新CSV,彻底避免覆盖问题。
2. 修复你脚本里的隐藏bug
除了CSV覆盖,你的脚本还有几个会导致数据不准确的小问题,顺便一起改了:
- Item实例化位置错了:你把
items = QuotetutorialItem()放在循环外面,循环里复用同一个Item对象,这会导致最后yield的Item数据被后面的循环项覆盖!必须把Item实例化放到循环里面。 - 全局XPath写法不规范:
industry和state是整个页面的全局数据,不需要在循环里每次取,而且你的XPath没加.,虽然当前页面没问题,但写法不严谨,容易出bug。 - 页码截取太脆弱:用
np.rfind('=')截取页码参数,万一URL里有多个=就会出错,用urlparse处理URL参数更可靠。
修改后的完整脚本:
import scrapy from scrapy.selector import Selector from ..items import QuotetutorialItem from urllib.parse import urlparse, parse_qs, urlencode class QuotesSpider(scrapy.Spider): name = 'quotes' # 可以添加多个起始URL start_urls = [ 'https://www.jobs2careers.com/results3.php?q=Fashion&l=Miami%2C+FL&s=00', 'https://www.jobs2careers.com/results3.php?q=Tech&l=New+York%2C+NY&s=00' ] def parse(self, response): s = Selector(response) # 全局数据只取一次,放循环外面 url = response.url industry = s.xpath("//input[@id='search_q']/@value").get() state = s.xpath("//*[@id='results_controller']/div[1]/div[1]/div/div/div/div/a[3]/text()").get() quote = s.xpath("//div[@id='jobs_container']/div") for q in quote: # 每个循环项都新建一个Item实例,避免数据覆盖 items = QuotetutorialItem() company_name = q.xpath(".//div[@class='companyname']/span[@class='company']/text()").get() job_title = q.xpath(".//div[@class='title title1 hidden-xs']/text()").get() # 处理空值,避免输出乱码或空字符串 items['url'] = url items['industry'] = industry.strip() if industry else None items['state'] = state.strip() if state else None items['company_name'] = company_name.strip() if company_name else None items['job_title'] = job_title.strip() if job_title else None yield items # 用urlparse处理页码,更稳定 parsed_url = urlparse(response.url) query_params = parse_qs(parsed_url.query) current_s = int(query_params.get('s', ['00'])[0]) num_text = s.xpath("//h1[@class='result2']//text()").get() if num_text: num = int(num_text.split("\n")[0].replace(',', '')) num = min(num, 1000) # 限制最大爬取量 next_s = current_s + 10 if next_s < num: query_params['s'] = str(next_s) next_page_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}?{urlencode(query_params, doseq=True)}" yield response.follow(next_page_url, callback=self.parse)
3. 关于“多Spider运行”的疑问
如果你是同时运行多个不同的Spider,默认每个Spider会用同一个FEEDS配置,这时候如果文件名相同,确实会互相覆盖。解决方法是给每个Spider单独配置FEEDS:
class QuotesSpider(scrapy.Spider): name = 'quotes' custom_settings = { 'FEEDS': { 'quotes_jobs.csv': { 'format': 'csv', 'overwrite': False } } } # ... 其他代码
每个Spider用不同的文件名,就不会互相干扰了。
内容的提问来源于stack exchange,提问作者Aleksandar Ciric
相关产品推荐
相关产品推荐

