Can't get Scrapy to parse and follow 301, 302 redirects
<p>I'm trying to write a very simple website crawler to list URLs along with referrer and status codes for 200, 301, 302 and 404 http status codes.</p>

<p>Turns out that Scrapy works great and my script uses it correctly to crawl the website and can list urls with 200 and 404 status codes without problems.</p>

<p><strong>The problem is:</strong> I can't find how to have scrapy follow redirects AND parse/output them. I can get one to work but not both.</p>

<p>What I've tried so far:</p>

<ul>
<li><p>setting <code>meta={'dont_redirect':True}</code> and setting <code>REDIRECTS_ENABLED = False</code></p></li>
<li><p>adding 301, 302 to handle_httpstatus_list</p></li>
<li><p>changing settings specified in the redirect middleware doc</p></li>
<li><p>reading the redirect middleware code for insight</p></li>
<li><p>various combo of all of the above</p></li>
<li><p>other random stuff</p></li>
</ul>

<p>Here's the <a href="https://gitlab.savoirfairelinux.com/fpotvin/easy-scrapy/tree/master" rel="nofollow">public repo</a> if you want to take a look at the code.</p>

<p>If you want to parse 301 and 302 responses, and follow them at the same time, ask for 301 and 302 to be processed by your callback and mimick the behavior of RedirectMiddleware.</p>

<h2>Test 1 (not working)</h2>

<p>Let's illustrate with a simple spider to start with (not working as you intend yet):</p>

<pre><code>import scrapy


class HandleSpider(scrapy.Spider):
    name = "handle"
    start_urls = (
        'https://httpbin.org/get',
        'https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F',
    )
    def parse(self, response):
        self.logger.info("got response for %r" % response.url)
</code></pre>

<p>Right now, the spider asks for 2 pages, and the 2nd one should redirect to <a href="http://www.example.com" rel="nofollow">http://www.example.com</a></p>

<pre><code>$ scrapy runspider test.py
2016-09-30 11:28:17 [scrapy] INFO: Scrapy 1.1.3 started (bot: scrapybot)
2016-09-30 11:28:18 [scrapy] DEBUG: Crawled (200) &lt;GET https://httpbin.org/get&gt; (referer: None)
2016-09-30 11:28:18 [scrapy] DEBUG: Redirecting (302) to &lt;GET http://example.com/&gt; from &lt;GET https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F&gt;
2016-09-30 11:28:18 [handle] INFO: got response for 'https://httpbin.org/get'
2016-09-30 11:28:18 [scrapy] DEBUG: Crawled (200) &lt;GET http://example.com/&gt; (referer: None)
2016-09-30 11:28:18 [handle] INFO: got response for 'http://example.com/'
2016-09-30 11:28:18 [scrapy] INFO: Spider closed (finished)
</code></pre>

<p>The 302 is handled by <code>RedirectMiddleware</code> automatically and it does not get passed to your callback.</p>

<h2>Test 2 (still not quite right)</h2>

<p>Let's configure the spider to handle 301 and 302s in the callback, <a href="https://doc.scrapy.org/en/latest/topics/downloader-middleware.html?#module-scrapy.downloadermiddlewares.redirect" rel="nofollow">using <code>handle_httpstatus_list</code></a>:</p>

<pre><code>import scrapy


class HandleSpider(scrapy.Spider):
    name = "handle"
    start_urls = (
        'https://httpbin.org/get',
        'https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F',
    )
    handle_httpstatus_list = [301, 302]
    def parse(self, response):
        self.logger.info("got response %d for %r" % (response.status, response.url))
</code></pre>

<p>Let's run it:</p>

<pre><code>$ scrapy runspider test.py
2016-09-30 11:33:32 [scrapy] INFO: Scrapy 1.1.3 started (bot: scrapybot)
2016-09-30 11:33:32 [scrapy] DEBUG: Crawled (200) &lt;GET https://httpbin.org/get&gt; (referer: None)
2016-09-30 11:33:32 [scrapy] DEBUG: Crawled (302) &lt;GET https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F&gt; (referer: None)
2016-09-30 11:33:33 [handle] INFO: got response 200 for 'https://httpbin.org/get'
2016-09-30 11:33:33 [handle] INFO: got response 302 for 'https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F'
2016-09-30 11:33:33 [scrapy] INFO: Spider closed (finished)
</code></pre>

<p>Here, we're missing the redirection.</p>

<h2>Test 3 (working)</h2>

<p>Do the <a href="https://github.com/scrapy/scrapy/blob/ebef6d7c6dd8922210db8a4a44f48fe27ee0cd16/scrapy/downloadermiddlewares/redirect.py#L54" rel="nofollow">same as RedirectMiddleware</a> but in the spider callback:</p>

<pre><code>from six.moves.urllib.parse import urljoin

import scrapy
from scrapy.utils.python import to_native_str


class HandleSpider(scrapy.Spider):
    name = "handle"
    start_urls = (
        'https://httpbin.org/get',
        'https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F',
    )
    handle_httpstatus_list = [301, 302]
    def parse(self, response):
        self.logger.info("got response %d for %r" % (response.status, response.url))

        # do something with the response here...

        # handle redirection
        # this is copied/adapted from RedirectMiddleware
        if response.status &gt;= 300 and response.status &lt; 400:

            # HTTP header is ascii or latin1, redirected url will be percent-encoded utf-8
            location = to_native_str(response.headers['location'].decode('latin1'))

            # get the original request
            request = response.request
            # and the URL we got redirected to
            redirected_url = urljoin(request.url, location)

            if response.status in (301, 307) or request.method == 'HEAD':
                redirected = request.replace(url=redirected_url)
                yield redirected
            else:
                redirected = request.replace(url=redirected_url, method='GET', body='')
                redirected.headers.pop('Content-Type', None)
                redirected.headers.pop('Content-Length', None)
                yield redirected
</code></pre>

<p>And run the spider again:</p>

<pre><code>$ scrapy runspider test.py
2016-09-30 11:45:20 [scrapy] INFO: Scrapy 1.1.3 started (bot: scrapybot)
2016-09-30 11:45:21 [scrapy] DEBUG: Crawled (302) &lt;GET https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F&gt; (referer: None)
2016-09-30 11:45:21 [scrapy] DEBUG: Crawled (200) &lt;GET https://httpbin.org/get&gt; (referer: None)
2016-09-30 11:45:21 [handle] INFO: got response 302 for 'https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F'
2016-09-30 11:45:21 [handle] INFO: got response 200 for 'https://httpbin.org/get'
2016-09-30 11:45:21 [scrapy] DEBUG: Crawled (200) &lt;GET http://example.com/&gt; (referer: https://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F)
2016-09-30 11:45:21 [handle] INFO: got response 200 for 'http://example.com/'
2016-09-30 11:45:21 [scrapy] INFO: Spider closed (finished)
</code></pre>

<p>We got redirected to <a href="http://www.example.com" rel="nofollow">http://www.example.com</a> and we also got the response through our callback.</p>

python scrapy