【问题标题】:Can't get desired results using try/except clause within scrapy在 scrapy 中使用 try/except 子句无法获得所需的结果
【发布时间】:2019-09-18 07:45:39
【问题描述】:

我在scrapy 中编写了一个脚本,通过get_proxies() 方法使用新生成的代理发出代理请求。我使用requests 模块来获取代理,以便在脚本中重用它们。我要做的是从landing page 解析所有电影链接,然后从target page 获取每部电影的名称。我的以下脚本可以使用代理轮换。

我知道有一种更简单的方法来更改代理,就像这里描述的 HttpProxyMiddleware 但我仍然想坚持我在这里尝试的方式。

website link

这是我目前的尝试(它一直使用新的代理来获取有效的响应,但每次都得到503 Service Unavailable):

import scrapy
import random
import requests
from itertools import cycle
from bs4 import BeautifulSoup
from scrapy.crawler import CrawlerProcess

def get_proxies():   
    response = requests.get("https://www.us-proxy.org/")
    soup = BeautifulSoup(response.text,"lxml")
    proxy = [':'.join([item.select_one("td").text,item.select_one("td:nth-of-type(2)").text]) for item in soup.select("table.table tbody tr") if "yes" in item.text]
    return proxy

class ProxySpider(scrapy.Spider):
    name = "proxiedscript"
    handle_httpstatus_list = [503]
    proxy_vault = get_proxies()
    check_url = "https://yts.am/browse-movies"

    def start_requests(self):
        random.shuffle(self.proxy_vault)
        proxy_url = next(cycle(self.proxy_vault))
        request = scrapy.Request(self.check_url,callback=self.parse,dont_filter=True)
        request.meta['https_proxy'] = f'http://{proxy_url}'
        yield request

    def parse(self,response):
        print(response.meta)
        if "DDoS protection by Cloudflare" in response.css(".attribution > a::text").get():
            random.shuffle(self.proxy_vault)
            proxy_url = next(cycle(self.proxy_vault))
            request = scrapy.Request(self.check_url,callback=self.parse,dont_filter=True)
            request.meta['https_proxy'] = f'http://{proxy_url}'
            yield request

        else:
            for item in response.css(".browse-movie-wrap a.browse-movie-title::attr(href)").getall():
                nlink = response.urljoin(item)
                yield scrapy.Request(nlink,callback=self.parse_details)

    def parse_details(self,response):
        name = response.css("#movie-info h1::text").get()
        yield {"Name":name}

if __name__ == "__main__":
    c = CrawlerProcess({'USER_AGENT':'Mozilla/5.0'})
    c.crawl(ProxySpider)
    c.start()

为了确定请求是否被代理,我打印了response.meta 并且可以得到类似{'https_proxy': 'http://142.93.127.126:3128', 'download_timeout': 180.0, 'download_slot': 'yts.am', 'download_latency': 0.237013578414917, 'retry_times': 2, 'depth': 0} 的结果。

由于我过度使用链接来检查scrapy 中代理请求的工作方式,此时我收到503 Service Unavailable 错误,我可以在响应DDoS protection by Cloudflare 中看到这个关键字。但是,当我尝试使用 requests 模块应用我在此处实现的相同逻辑时,我得到了有效的响应。

My earlier question: why I can't get the valid response as (I suppose) I'm using proxies in the right way? [solved]

赏金问题:如何在我的脚本中定义 try/except 子句,以便在与某个代理发生连接错误时尝试使用不同的代理?

【问题讨论】:

  • 你能贴出你使用 requests 模块而不是 scrapy 时得到的 headers 吗?
  • 当然。这是requests.get(itemlink,proxies=proxy,headers={"User-Agent":"Mozilla/5.0"})@Edeki Okoh。
  • 来自标头的实际响应而不是代码本身。查看代理以什么格式发送

标签: python python-3.x web-scraping scrapy


【解决方案1】:

根据scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddlewaredocs (和source
预计使用proxy 元键(不是https_proxy

#request.meta['https_proxy'] = f'http://{proxy_url}'  
request.meta['proxy'] = f'http://{proxy_url}'

由于 scrapy 没有收到有效的元密钥 - 您的 scrapy 应用程序没有使用代理

【讨论】:

    【解决方案2】:

    start_requests() 函数只是入口点。在后续请求中,您需要将此元数据重新提供给 Request 对象。

    此外,错误可能发生在两个级别:代理和目标服务器

    我们需要处理来自代理和目标服务器的错误响应代码。 middelware 将代理错误返回给errback 函数。从response.status解析时可以处理目标服务器响应

    import scrapy
    import random
    import requests
    from itertools import cycle
    from bs4 import BeautifulSoup
    from scrapy.crawler import CrawlerProcess
    
    
    def get_proxies():
        response = requests.get("https://www.us-proxy.org/")
        soup = BeautifulSoup(response.text, "lxml")
        proxy = [':'.join([item.select_one("td").text, item.select_one("td:nth-of-type(2)").text]) for item in
                 soup.select("table.table tbody tr") if "yes" in item.text]
        # proxy = ['https://52.0.0.1:8090', 'https://52.0.0.2:8090']
        return proxy
    
    
    def get_random_proxy(proxy_vault):
        random.shuffle(proxy_vault)
        proxy_url = next(cycle(proxy_vault))
        return proxy_url
    
    
    class ProxySpider(scrapy.Spider):
        name = "proxiedscript"
        handle_httpstatus_list = [503, 502, 401, 403]
        check_url = "https://yts.am/browse-movies"
        proxy_vault = get_proxies()
    
        def handle_middleware_errors(self, *args, **kwargs):
            # implement middleware error handling here
            print('Middleware Error')
            # retry request with different proxy
            yield self.make_request(url=args[0].request._url, callback=args[0].request._meta['callback'])
    
        def start_requests(self):
            yield self.make_request(url=self.check_url, callback=self.parse)
    
        def make_request(self, url, callback, dont_filter=True):
            return scrapy.Request(url,
                                  meta={'proxy': f'https://{get_random_proxy(self.proxy_vault)}', 'callback': callback},
                                  callback=callback,
                                  dont_filter=dont_filter,
                                  errback=self.handle_middleware_errors)
    
        def parse(self, response):
            print(response.meta)
            try:
                if response.status != 200:
                    # implement server status code handling here - this loops forever
                    print(f'Status code: {response.status}')
                    raise
                else:
                    for item in response.css(".browse-movie-wrap a.browse-movie-title::attr(href)").getall():
                        nlink = response.urljoin(item)
                        yield self.make_request(url=nlink, callback=self.parse_details)
            except:
                # if anything goes wrong fetching the lister page, try again
                yield self.make_request(url=self.check_url, callback=self.parse)
    
        def parse_details(self, response):
            print(response.meta)
            try:
                if response.status != 200:
                    # implement server status code handeling here - this loops forever
                    print(f'Status code: {response.status}')
                    raise
                name = response.css("#movie-info h1::text").get()
                yield {"Name": name}
            except:
                # if anything goes wrong fetching the detail page, try again
                yield self.make_request(url=response.request._url, callback=self.parse_details)
    
    
    if __name__ == "__main__":
        c = CrawlerProcess({'USER_AGENT': 'Mozilla/5.0'})
        c.crawl(ProxySpider)
        c.start()
    

    【讨论】:

    • 对此的改进是在抓取时创建一个工作代理列表并在随机代理上使用它们。
    • 中间件出错时,调用函数handle_middleware_errors。据我所知,这总是带有错误列表。从我所做的测试中,我可以看到错误总是在索引 0 args[0] 上。该错误还包含原始request,并且原始请求始终具有_url_meta['callback'](因为我们明确地将每个请求的回调设置为元字典的一部分)。这允许使用原始参数再次重试make_request()
    • 你能帮我理解为什么args[0].request._urlargs[0].request.urlhandle_middleware_errors() 中的工作方式相同,即使没有下划线 _ 在后一个。谢谢。
    • 我可能错过了他们有可用的公共属性这一事实。我会假设它们在该函数的范围内总是相同的,在这种情况下,从编码的角度来看,采用公共属性request.url。 python中带下划线的函数和属性常用来传达函数或属性是private而不是publicstackoverflow.com/questions/14671487/…
    • 您想知道为什么您在make_requests() 方法@tvgriek 中使用了两次callback 吗?谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-30
    • 2022-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-13
    • 2013-04-10
    相关资源
    最近更新 更多