【问题标题】:Avoid bad requests due to relative urls避免由于相对 url 而产生的错误请求
【发布时间】:2013-11-04 13:54:26
【问题描述】:

我正在尝试使用 Scrapy 抓取一个网站,而我要抓取的每个页面的 url 都是使用这种相对路径编写的:

<!-- on page https://www.domain-name.com/en/somelist.html (no <base> in the <head>) -->
<a href="../../en/item-to-scrap.html">Link</a>

现在,在我的浏览器中,这些链接可以正常工作,您可以访问像 https://www.domain-name.com/en/item-to-scrap.html 这样的网址(尽管相对路径在层次结构中会向上备份两次而不是一次)

但我的 CrawlSpider 无法将这些 url 翻译成“正确”的,我得到的只是那种错误:

2013-10-13 09:30:41-0500 [domain-name.com] DEBUG: Retrying <GET https://www.domain-name.com/../en/item-to-scrap.html> (failed 1 times): 400 Bad Request

有没有办法解决这个问题,还是我错过了什么?

这是我的蜘蛛代码,相当基本(基于匹配“/en/item-*-scrap.html”的项目网址):

from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from scrapy.item import Item, Field

class Product(Item):
    name = Field()

class siteSpider(CrawlSpider):
    name = "domain-name.com"
    allowed_domains = ['www.domain-name.com']
    start_urls = ["https://www.domain-name.com/en/"]
    rules = (
        Rule(SgmlLinkExtractor(allow=('\/en\/item\-[a-z0-9\-]+\-scrap\.html')), callback='parse_item', follow=True),
        Rule(SgmlLinkExtractor(allow=('')), follow=True),
    )

    def parse_item(self, response):
        x = HtmlXPathSelector(response)
        product = Product()
        product['name'] = ''
        name = x.select('//title/text()').extract()
        if type(name) is list:
            for s in name:
                if s != ' ' and s != '':
                    product['name'] = s
                    break
        return product

【问题讨论】:

    标签: python scrapy web-crawler


    【解决方案1】:

    基本上,scrapy 使用http://docs.python.org/2/library/urlparse.html#urlparse.urljoin 通过加入 currenturl 和废弃的 url 链接来获取下一个 url。如果您加入您提供的网址作为示例,

    <!-- on page https://www.domain-name.com/en/somelist.html -->
    <a href="../../en/item-to-scrap.html">Link</a>
    

    返回的 url 与错误 scrapy 错误中提到的 url 相同。在 python shell 中试试这个。

    import urlparse 
    urlparse.urljoin("https://www.domain-name.com/en/somelist.html","../../en/item-to-scrap.html")
    

    urljoin 行为似乎是有效的。见:https://www.rfc-editor.org/rfc/rfc1808.html#section-5.2

    如果可能的话,你能不能通过你正在抓取的站点?

    有了这个认识,解决办法就可以了,

    1. 处理网址(删除这两个点和斜线)。在爬虫中生成。基本上覆盖 parse 或 _request_to_folow。

    爬虫来源:https://github.com/scrapy/scrapy/blob/master/scrapy/contrib/spiders/crawl.py

    1. 在下载中间件中操作url,这样可能会更干净。您删除了下载中间件的 process_request 中的 ../。

    中间件下载文档:http://scrapy.readthedocs.org/en/0.16/topics/downloader-middleware.html

    1. 使用基础蜘蛛并返回您想要进一步抓取的操纵网址请求

    basespider 的文档:http://scrapy.readthedocs.org/en/0.16/topics/spiders.html#basespider

    如果您有任何问题,请告诉我。

    【讨论】:

    • 感谢您的帮助。我正在尝试实施您的第二个解决方案,到目前为止没有任何行为变化。我相信我的中间件根本没有被调用,或者什么都不做。我找不到 process_request() 实现的任何示例,并且文档并不完全清楚 process_request 应该做什么(例如,如果我返回请求会发生什么?)请参阅我编辑的问题以跟进您的答案
    • 我认为您不需要返回请求。只需更改网址。我同意文档没有提及如果您返回请求会发生什么行为。
    • 对不起,我更改了我的方法以返回父方法的结果,并带有更正的 url。我相信这应该可以解决问题。现在我的问题是,当我运行蜘蛛爬网时,我的自定义下载器没有出现在启用的下载器中间件列表中。 (见问题)我可能没有正确添加它。
    • 我认为 DownloadMiddleWare 类不存在。您的自定义 DownloadMiddleWare 不需要继承任何类。
    • 好吧,我写的是 DOWNLOADER_MIDDLEWARE 而不是 DOWNLOADER_MIDDLEWARES。测试。
    【解决方案2】:

    感谢this answer,我终于找到了解决方案。我使用 process_links 如下:

    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.selector import HtmlXPathSelector
    from scrapy.item import Item, Field
    
    class Product(Item):
        name = Field()
    
    class siteSpider(CrawlSpider):
        name = "domain-name.com"
        allowed_domains = ['www.domain-name.com']
        start_urls = ["https://www.domain-name.com/en/"]
        rules = (
            Rule(SgmlLinkExtractor(allow=('\/en\/item\-[a-z0-9\-]+\-scrap\.html')), process_links='process_links', callback='parse_item', follow=True),
            Rule(SgmlLinkExtractor(allow=('')), process_links='process_links', follow=True),
        )
    
        def parse_item(self, response):
            x = HtmlXPathSelector(response)
            product = Product()
            product['name'] = ''
            name = x.select('//title/text()').extract()
            if type(name) is list:
                for s in name:
                    if s != ' ' and s != '':
                        product['name'] = s
                        break
            return product
    
        def process_links(self,links):
            for i, w in enumerate(links):
                w.url = w.url.replace("../", "")
                links[i] = w
            return links
    

    【讨论】:

      猜你喜欢
      • 2018-11-18
      • 2015-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多