【问题标题】:Scrapy not executing in the correct orderScrapy 未按正确顺序执行
【发布时间】:2016-08-31 22:44:13
【问题描述】:

我目前正在开发一个网络爬虫,它应该访问目录中的网站列表,访问网站的 CSS 样式表,检查 @media 标签(我知道这是检查响应式设计的基本方法还有其他极端情况需要考虑),并将所有不使用响应式设计的网站打印到文件中。

我相当肯定我实际检查 CSS 中是否有 @media 标签的方法工作正常,但是蜘蛛在确定是否找到带有 @media 标签的文件之前并没有访问所有 CSS 文件。我有一个测试文件,它在程序运行时记录调试输出,它显示了奇怪的模式,例如完成检查所有 CSS 文件,然后打印出它在文件中找到的内容,这是不应该发生的。

我希望有人可以查看我的代码并帮助我确定为什么这没有按照我想要的顺序发生。作为参考,目标是:

  1. 访问列表中的网站
  2. 访问该网站 HTML 头部元素中的每个 CSS 文件
  3. 如果找到@media 标签,我们就完成了,网站使用响应式设计
  4. 如果没有,请继续检查更多 CSS 文件
  5. 如果没有 CSS 文件包含 @media 标记,则该网站不使用响应式设计,应添加到列表中

这是我的代码(并非所有内容都能完美运行 - 例如,程序超时,因为我还没有使用 TimeOutError,但在大多数情况下,我觉得这应该可以正确评估网站,并且它没有这样做):

import scrapy
import re
import os.path
from scrapy.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from twisted.internet.error import TimeoutError
import time

class LCCISpider(CrawlSpider):
    name = "lcci"
    start_urls = ["http://www.lancasterchamber.com/busdirectory.aspx?mode=category"]
    #Calls parse_item for every category link on main page
    rules = (Rule(SgmlLinkExtractor(restrict_xpaths=('//div[@id="catListingResults"]/table/tr')), 
            callback = 'parse_item', follow = True),)
    website_list = []
    found_media = False

    #Called for each category
    def parse_item(self, response):
        #For each site on the page, calls parse_website


        sites = response.xpath('//div[@id="busListingResults"]/table/tr')
        for site in sites:
            urls = site.xpath('.//td/a[4]/@href').extract()
            for url in urls:
                if len(url) == 0:
                    continue
                else:
                    new_site = response.urljoin(url)
                    yield scrapy.Request(new_site, callback=self.parse_website,
                                                    errback=self.errback_website)




    def parse_website(self, response):

        f = open('output2.txt', 'a')
        f.write("NOW VISITING")
        f.flush()
        f.write(response.url)
        f.flush()
        f.write("\n")
        f.flush()
        f.close()
        #reset found_media to false for each website
        self.found_media = False
        #for every link in the header, check potential css for @media tag
        for href in response.css("head > link::attr('href')"):
            url = response.urljoin(href.extract())
            #if @media tag has not been found, continue checking css
            if self.found_media == False:
                #Call check_css for the url of the css file
                yield scrapy.Request(url, callback=self.check_css,
                                          errback=self.errback_website)

                f = open('output2.txt', 'a')
                f.write("step\n")
                f.flush()
                f.close()
            else:
                break

        #if no @media tag is found in any link in the header, add the url to the website_list

        if self.found_media == False:
            #self.website_list.append(response.url)
            f = open('output2.txt', 'a')
            f.write("No @media tag in")
            f.flush()
            f.write(response.url)
            f.flush()
            f.write("\n")
            f.flush()
            f.close()

            f = open('outputfalse2.txt', 'a')
            f.write(response.url)
            f.write("\n")
            f.close()

        else:
            f = open('outputtrue.txt', 'a')
            f.write(reponse.url)
            f.write("\n")
            f.close()

    def check_css(self, response):

        #Just a way of converting url into a string, the ".txt" is otherwise meaningless
        string = str(response.url)
        f = open('output2.txt', 'a')
        f.write("Checking CSS in ")
        f.write(response.url)
        f.write("\n")
        f.flush()
        f.close()
        #only perform regex search if it's a .css file
        if (string[-4:] == ".css"): 
            media_match = re.search(r'@media', response.body, flags=0)
            if media_match != None:
                f = open('output2.txt', 'a')
                f.write("found @media tag in " + response.url + "\n")
                f.flush()
                #If an @media tag is found, set found_media to True
                self.found_media = True
                f.close()
        else:
            f = open('output2.txt', 'a')
            f.write("not css")
            f.flush()
            f.close()

    def errback_website(self, failure):
        if failure.check(TimeoutError):
            request = failure.request
            self.logger.error = ('TimeoutError on %s', request.url)

【问题讨论】:

  • 我注意到的第一件事是您使用self.found_media 来控制蜘蛛状态。对于异步系统,您应该采取的方法是继续前进,直到找到一些东西,然后提高 CloseSpider 以结束。

标签: python css web scrapy web-crawler


【解决方案1】:

我浏览了一下,忍不住完成了这项工作。这是完全清理的代码。 在逻辑方面几乎没有变化。 它现在所做的是:

  1. 连接到网站
  2. 获取所有类别
  3. 从类别中获取所有网站
  4. 连接到每个网站的首页
  5. 寻找.css链接
  6. 连接到.css 链接 6.1 如果media 正则表达式匹配yield item与css url和item

这里唯一的问题是,由于scrapy 的异步特性,您最终会产生大量重复项,因为您可能会同时抓取多个 .css 文件。为此,我们可以使用简单的管道来检测和删除重复项。 为了将来参考,您不应使用文件写入进行调试。看看scrapy shell,你甚至可以在parse里面使用它来在爬取过程中打开shell,比如:

def parse(self, response):
    inspect_response(response, self)

这是工作蜘蛛:

import re
from scrapy.spiders import CrawlSpider, Rule
from scrapy.exceptions import DropItem
from scrapy.linkextractors import LinkExtractor
from twisted.internet.error import TimeoutError
from scrapy import Request


class DupePipeline(object):
    def __init__(self):
        self.known_websites = set()

    def process_item(self, item, spider):
        if item['website'] in self.known_websites:
            raise DropItem('duplicate')
        self.known_websites.add(item['website'])
        return item


class LCCISpider(CrawlSpider):
    name = "lcci"
    start_urls = ["http://www.lancasterchamber.com/busdirectory.aspx?mode=category"]
    custom_settings = {
        'ROBOTSTXT_OBEY': False,
        'ITEM_PIPELINES': {
            'myproject.spiders.spider.DupePipeline': 666,
        }
    }
    # Calls parse_item for every category link on main page
    rules = (Rule(LinkExtractor(restrict_xpaths=['//div[@id="catListingResults"]/table/tr']),
                  callback='parse_item', follow=True),)  # why follow?

    # Called for each category
    def parse_item(self, response):
        # For each site on the page, calls parse_website
        sites = response.xpath('//div[@id="busListingResults"]/table/tr')
        for site in sites:
            urls = site.xpath('.//td/a[4]/@href').extract()
            for url in urls:
                if not url:
                    continue
                new_site = response.urljoin(url)
                yield Request(new_site,
                              callback=self.parse_website,
                              errback=self.errback_website)

    def parse_website(self, response):
        # for every link in the header, check potential css for @media tag
        for href in response.css("head > link::attr('href')").extract():
            if not href.endswith('.css'):  # only css files
                continue
            yield Request(response.urljoin(href),
                          meta={'website': response.url},
                          callback=self.check_css,
                          errback=self.errback_website)

    def check_css(self, response):
        media_match = re.search(r'@media', response.body, flags=0)
        if media_match:
            # return item!
            yield {'url': response.url,
                   'website': response.meta['website']}

    def errback_website(self, failure):
        if failure.check(TimeoutError):
            request = failure.request
            self.logger.error = ('TimeoutError on %s', request.url)

使用scrapy crawl lcci -o test.json 运行几分钟后的结果我得到了这个:http://pastebin.com/raw/kfsTKqUY

【讨论】:

  • 我知道我发布这篇文章已经快两周了,但是随着学校的开学,我从这个项目中休息了一段时间,想知道你是否可以提供更多建议。我不确定我是否完全理解管道是如何工作的,并且正在尝试反转此代码的工作方式,以便包含所有包含 @media 标签的网站的 .json 文件,而不是包含所有网站不是。这是一个简单的解决方法,还是需要像交叉引用 json 文件中的站点列表和所有站点的列表来找到剩余的站点?
  • @twsp 如果您想检索所有不包含媒体匹配的网站,只需将if media_match: 行更改为if not media_match:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-10
  • 2019-07-01
  • 2023-01-17
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 2021-09-15
相关资源
最近更新 更多