Implement a web crawler using a provided API
AnthropicMachine Learning Engineer · OnsiteLast updated
This problem tests your ability to design a multi-threaded web crawler that efficiently traverses a site while respecting domain constraints and avoiding redundant work.
Web Crawler Implementation
You are tasked with building a web crawler that extracts all unique URLs from a given starting page, subject to specific constraints. You are provided with a utility function fetch_urls(url) which returns a list of URLs linked from the input page. Your crawler must only visit URLs belonging to the same hostname as the starting URL.
Requirements
- Implement a function
crawl(start_url, max_depth)that performs a breadth-first search of the web graph. - The crawler must be multi-threaded to speed up fetching.
- Prevent redundant visits and infinite loops caused by cyclical links.
- Use thread-safe data structures to manage the set of visited URLs.
Function Signature
def crawl(start_url: str, max_depth: int) -> set:
# Returns a set of all visited URLs within the allowed depth
Constraints
max_depthwill be a non-negative integer.- You must not visit the same URL twice.
- The crawler should stop once all reachable URLs within
max_depthhave been processed.
Examples
Example 1
Input: start_url = "http://example.com/home", max_depth = 1
Output: {"http://example.com/home", "http://example.com/about", "http://example.com/contact"}
Explanation: The crawler visits the home page and fetches all linked URLs from the same host, stopping at depth 1.
Edge Cases
- The starting URL is invalid or the fetcher returns an empty list.
- Links point to external hostnames (e.g.,
google.cominsideexample.com). - High volume of concurrent requests causing rate-limiting (ignore actual network latency, focus on structure).
Hints
Hint 1
Use a thread-safe set (like a set protected by a Lock or a concurrent hash set) to keep track of already visited URLs to avoid redundant processing.
Hint 2
Structure your traversal as a queue of tuples containing `(url, current_depth)`, and use a thread pool to process these entries concurrently.
Hint 3
Use `urllib.parse.urlparse` to easily extract the hostname of a URL to ensure your crawler stays within the allowed domain scope.
Follow-up questions
- How would you modify this crawler to be polite and avoid overwhelming a server if you were hitting it with 50+ threads?
- If the set of visited URLs becomes too large to fit in memory, what storage architecture would you use to maintain the 'visited' state?
- How would you change your design if the web pages were rendered dynamically using JavaScript, meaning `fetch_urls` is no longer sufficient?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.