【问题标题】:Scrapy output multiple item elements with xpath to single csv?使用xpath将多个项目元素Scrapy输出到单个csv?
【发布时间】:2014-02-06 02:12:48
【问题描述】:

我正在尝试从包含各种 HTML 元素和一系列嵌套表格的页面中抓取项目。

我有一些代码正在工作,成功地从表 X 中抓取 class="ClassA" 并将表元素输出到一系列项目中,例如公司地址、电话号码、网站地址等。

我想在我输出的这个列表中添加一些额外的项目,但是要抓取的其他项目不在同一个表中,有些甚至根本不在表中,例如 页面另一部分的标签。

如何使用 xpath 过滤器将其他一些项目添加到我的输出中并让它们出现在相同的数组/输出结构中?我注意到,如果我从另一个表中刮取额外的表项(即使该表具有完全相同的 CLASS 名称和 ID),这些其他项的 CSV 输出也会在 CSV 中的不同行上输出,而不是保持 CSV 结构完整:(

我确定必须有一种方法可以让项目在 csv 输出中保持统一,即使它们是从页面上略有不同的区域抓取的?希望它只是一个简单的修复...

----- HTML 示例页面正在被抓取 -----

<html>
<head></head>
<body>

< // huge amount of other HTML and tables NOT to be scraped >

<h2>HEADING TO BE SCRAPED - Company Name</h2>
<p>Company Description</p>

< table cellspacing="0" class="contenttable company-details">
<tr>
  <th>Item Code</th>
  <td>IT123</td>
</tr>
  <th>Listing Date</th>
  <td>12 September, 2011</td>
</tr>
<tr>
  <th>Internet Address</th>
  <td class="altrow"><a href="http://www.website.com/" target="_top">http://www.website.com/</a></td>
</tr>
<tr>
  <th>Office Address</th>
  <td>123 Example Street</td>
</tr>    
<tr>
  <th>Office Telephone</th>
  <td>(01) 1234 5678</td>
</tr>       
</table>

<table cellspacing="0" class="contenttable" id="staff">
<tr><th>Management Names</th></tr>
<tr>
    <td>
    Mr John Citizen (CEO)<br/>Mrs Mary Doe (Director)<br/>Dr J. Watson (Manager)<br/>
    </td>
</tr>
</table>

<table cellspacing="0" class="contenttable company-details">    
<tr>
    <th>Contact Person</th>
    <td>        
    Mr John Citizen<br/>        
    </td>
</tr>   
<tr>
    <th class=principal>Company Mission</th>
    <td>ACME Corp is a retail sales company.</td>
</tr>   
</table>

</body>
</html>

---- 抓取代码示例----

from scrapy.spider import Spider
from scrapy.selector import Selector
from my.items import AsxItem

class MySpider(Spider):
name = "my"
allowed_domains = ["website.com"]
start_urls = ["http://www.website.com/ABC" ]

def parse(self, response):
   sel = Selector(response)
   sites = sel.xpath('//table[@class="contenttable company-details"]')
   items = []

   for site in sites:
      item = MyItem()
      item['Company_name'] = site.xpath('.//h1//text()').extract()
      item['Item_Code'] = site.xpath('.//th[text()="Item Code"]/following-sibling::td//text()').extract()
      item['Listing_Date'] = site.xpath('.//th[text()="Listing Date"]/following-sibling::td//text()').extract()
      item['Website_URL'] = site.xpath('.//th[text()="Internet Address"]/following-sibling::td//text()').extract()
      item['Office_Address'] = site.xpath('.//th[text()="Office Address"]/following-sibling::td//text()').extract()
      item['Office_Phone'] = site.xpath('.//th[text()="Office Telephone"]/following-sibling::td//text()').extract()
      item['Company_Mission'] = site.xpath('//th[text()="Company Mission"]/following-sibling::td//text()').extract()
      yield item

输出到 CSV

scrapy crawl my -o items.csv -t csv

使用上面的示例代码,[companymission] 项目出现在 CSV 中与其他项目不同的行上(猜测是因为它在不同的表中)即使它具有相同的 CLASS 名称和 ID,并且另外我不确定如何抓取

字段,因为它超出了我当前 XPATH 站点过滤器的表结构?

我可以扩展站点 XPATH 过滤器以包含更多内容,但这不会降低效率并破坏一起过滤的意义吗?

这是调试日志的示例,您可以在其中看到公司任务由于某种原因被处理了两次,并且第一个循环是空的,这一定是它输出到 CSV 中的新行的原因,但是为什么??

{'Item_Code': [u'ABC'],
 'Listing_Date': [u'1 January, 2000'],
 'Office_Address': [u'Level 1, Some Street, SYDNEY, NSW, AUSTRALIA, 2000'],
 'Office_Fax': [u'(02) 1234 5678'],
 'Office_Phone': [u'(02) 1234 5678'],
 'Company_Mission': [],
 'Website_URL': [u'http://www.company.com']}
2014-02-06 16:32:13+1000 [my] DEBUG: Scraped from <200 http://www.website.com/Code=ABC>
{'Item_Code': [],
 'Listing_Date': [],
 'Office_Address': [],
 'Office_Fax': [],
 'Office_Phone': [],
 'Company_Mission': [u'The comapany is involved in retail, food and beverage, wholesale services.'],
 'Website_URL': []}

我完全困惑的另一件事是为什么项目在 CSV 中以与 HTML 页面上的项目和我在蜘蛛配置文件中定义的顺序完全不同的顺序吐出。 scrapy 是否以它喜欢的任何顺序完全异步运行返回项目?

【问题讨论】:

    标签: csv xpath web-scraping scrapy


    【解决方案1】:

    我了解到您想为此页面抓取 1 个项目,但 //table[@class="contenttable company-details"] 匹配您 HTML 内容中的 2 个表格元素,因此 for site in sites: 将运行两次,创建 2 个项目。

    对于每个表,如果 XPath 表达式是相对的——.//th[text()="Item Code"],将在当前表中应用它们。绝对 XPath 表达式(例如 //th[text()="Company Mission"])将从 HTML 文档的根元素中查找元素。

    您的示例输出只显示了一次"Company_Mission",而您说它出现了两次。而且因为您使用的是绝对 XPath 表达式,它确实应该出现两次。不确定输出是否与问题中您当前的蜘蛛代码匹配。

    所以,循环的第一次迭代,

        <table cellspacing="0" class="contenttable company-details">
        <tr>
          <th>Item Code</th>
          <td>IT123</td>
        </tr>
          <th>Listing Date</th>
          <td>12 September, 2011</td>
        </tr>
        <tr>
          <th>Internet Address</th>
          <td class="altrow"><a href="http://www.website.com/" target="_top">http://www.website.com/</a></td>
        </tr>
        <tr>
          <th>Office Address</th>
          <td>123 Example Street</td>
        </tr>    
        <tr>
          <th>Office Telephone</th>
          <td>(01) 1234 5678</td>
        </tr>       
        </table>
    

    你可以在其中刮:

    • 项目代码
    • 上市日期
    • Internet 地址 --> 网站 URL
    • 办公地址
    • 办公电话

    由于您使用的是绝对 XPath 表达式,//th[text()="Company Mission"]/following-sibling::td//text() 将在文档中的任何位置查找,而不仅仅是在第一个 &lt;table cellspacing="0" class="contenttable company-details"&gt;

    这些提取的字段进入它们自己的项目。

    然后是与您的 XPath 匹配 sites 的第二个表:

        <table cellspacing="0" class="contenttable company-details">    
        <tr>
            <th>Contact Person</th>
            <td>        
            Mr John Citizen<br/>        
            </td>
        </tr>   
        <tr>
            <th class=principal>Company Mission</th>
            <td>ACME Corp is a retail sales company.</td>
        </tr>   
        </table>
    

    为其实例化了一个新的MyItem(),并且在这里,除了“公司使命”的绝对 XPath 之外,没有任何 XPath 表达式匹配,因此在循环迭代结束时,您将得到一个只有“公司使命”的项目”。

    如果您确定您只期望此页面中只有 1 个且只有 1 个项目,您可以为所需的每个字段使用更长的 XPath,例如 //table[@class="contenttable company-details"]//th[text()="Item Code"]/following-sibling::td//text(),以便它匹配第一个或第二个表,

    并且只使用 1 个MyItem() 实例。

    此外,您可以尝试读写更短且更易于维护的 CSS 选择器:

    • “公司名称”sel.css('h2::text')
    • “项目代码”sel.css('table.company-details th:contains("Item Code") + td::text')
    • “上市日期”sel.css('table.company-details th:contains("Listing Date") + td::text')

    请注意,:contains() 可以通过下面的 cssselect 在 Scrapy 中使用,但它不是标准的(已从 CSS 规范中删除,但很方便),::text 伪元素选择器也是非标准的,而是一个 Scrapy 扩展,也很方便。

    【讨论】:

    • 感谢 Paul,这更能说明为什么循环似乎运行了两次,因为 table 元素出现了两次 :-) 我已将每个项目设置为使用更长的 XPath 选择器建议并调整站点变量以引用仅在文档中出现一次的元素,因此它现在只循环一次。情况看起来不错!
    【解决方案2】:

    guessing because its in a different table - 猜错了,表和item没有关联,其实数据从哪里来的没关系,只要设置item字段就行了。

    意味着您可以从任何地方获取 Company_name 和 Company_Mission。

    话虽如此,检查从//th[text()="Company Mission"] 返回的内容以及它在页面上出现的次数,而其他项 xpath 是相对的(以 . 开头)这是绝对的(以 // 开头) ,它可能会抓取一个项目列表,而不仅仅是一个

    【讨论】:

    • 所以我尝试了带有绝对(点)和不带(点)的 //th[text()="Company Mission"] 但两者仍然导致输出 CSV“公司任务”项目在所有其他 CSV 项目仅在现有行上用逗号分隔之后,移到新行上。仍然困惑为什么会这样?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    • 2020-01-11
    • 2017-03-01
    • 2012-08-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多