Following links and pagination

Scrapy · Essentials · 12 min

Real crawls span listing pages, detail pages and pagination, and Scrapy's request model makes that natural.

To crawl further, a callback yields requests. The convenient way is response.follow(url, callback=...):

  • It accepts relative URLs (/page/2/) and resolves them against the response URL.
  • It also accepts a selector for an <a> element directly: response.follow(a_selector, ...).
  • response.follow_all(css="li.next a", callback=self.parse) creates requests for every match.

The callback defaults to self.parse. Use different callbacks for different page types: parse for listing pages yields requests to detail pages with callback=self.parse_author, and a request for the next listing page with callback=self.parse.

Deduplication: Scrapy remembers request fingerprints and drops requests for URLs it has already seen, so following the same author link from many quotes fetches it once. Pass dont_filter=True to bypass that when needed.

To pass data from one page to the next, use cb_kwargs={"category": name}: the values arrive as keyword arguments of the callback. That's the clean replacement for stuffing data into request.meta.

Pagination ends naturally when there's no next link. Guard deep or infinite crawls with DEPTH_LIMIT or CLOSESPIDER_PAGECOUNT, and keep allowed_domains set so links to other sites are ignored.

Example

import scrapy

class AuthorsSpider(scrapy.Spider):
    name = "authors"
    allowed_domains = ["quotes.toscrape.com"]
    start_urls = ["https://quotes.toscrape.com/"]

    def parse(self, response):
        for q in response.css("div.quote"):
            about = q.css("span a")      # the (about) link
            yield response.follow(about[0], callback=self.parse_author,
                                  cb_kwargs={"first_quote": q.css("span.text::text").get()})
        yield from response.follow_all(css="li.next a", callback=self.parse)

    def parse_author(self, response, first_quote):
        yield {
            "name": response.css("h3.author-title::text").get().strip(),
            "born": response.css("span.author-born-date::text").get(),
            "first_quote_seen": first_quote[:30],
        }
# Each author page is fetched once, even if linked from many quotes.

Remember

  • response.follow resolves relative URLs and accepts <a> selectors
  • Use separate callbacks per page type
  • Duplicate URLs are filtered automatically (dont_filter=True to override)
  • cb_kwargs passes data to the next callback

Watch out for

  • Building absolute URLs by string concatenation instead of response.follow / response.urljoin.

Check yourself

  1. Twenty quotes link to the same author page. How many times does Scrapy download it by default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scrapy.org/en/latest/intro/tutorial.html#following-links