3 min read

WordPress Broken Link Checker

This utility crawls a WordPress website (or any website) to identify broken links. It starts from a homepage, recursively finds internal pages, and checks the status code of every link found. It maintains a cache of checked URLs to avoid redundant requests and improve performance.

Modules Used:

  • requests: To fetch pages and check link status codes.
  • beautifulsoup4: To parse HTML and extract <a> tags.
  • argparse: To handle command-line arguments.
  • urllib.parse: To handle URL joining and domain parsing.

Installation

pip install requests beautifulsoup4

The Code

Save this as link_checker.py.

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import argparse
import sys

def check_link(url, checked_cache):
    """Checks the status code of a URL, using a cache to avoid duplicates."""
    if url in checked_cache:
        return checked_cache[url]

    try:
        headers = {'User-Agent': 'Mozilla/5.0 (compatible; BrokenLinkBot/1.0)'}
        # stream=True prevents downloading the entire body for large files
        r = requests.get(url, headers=headers, timeout=5, stream=True)
        status = r.status_code
        r.close()
    except Exception:
        status = 0 # Connection error

    checked_cache[url] = status
    return status

def crawl_site(start_url, max_pages=50):
    domain = urlparse(start_url).netloc
    queue = [start_url]
    visited_pages = set()
    checked_links = {} # Cache: {url: status_code}
    broken_links = []  # List: [(source_page, broken_link, status)]

    print(f"Starting scan of {start_url} (Limit: {max_pages} pages)...")

    while queue and len(visited_pages) < max_pages:
        current_page = queue.pop(0)

        if current_page in visited_pages:
            continue

        print(f"Scanning page {len(visited_pages)+1}: {current_page}")
        visited_pages.add(current_page)

        try:
            headers = {'User-Agent': 'Mozilla/5.0 (compatible; BrokenLinkBot/1.0)'}
            response = requests.get(current_page, headers=headers, timeout=10)

            # Skip if page load failed or not HTML
            if response.status_code != 200 or 'text/html' not in response.headers.get('Content-Type', ''):
                continue

            soup = BeautifulSoup(response.text, 'html.parser')
            links = soup.find_all('a', href=True)

            for link in links:
                href = link['href']
                full_url = urljoin(current_page, href)
                # Remove fragments (#section)
                full_url = full_url.split('#')[0]

                if not full_url.startswith(('http://', 'https://')):
                    continue

                # Check status
                status = check_link(full_url, checked_links)

                if status >= 400 or status == 0:
                    print(f"  [BROKEN] {full_url} (Status: {status})")
                    broken_links.append((current_page, full_url, status))

                # If internal link and not visited, add to queue for crawling
                if domain in urlparse(full_url).netloc:
                    if full_url not in visited_pages and full_url not in queue:
                        queue.append(full_url)

        except Exception as e:
            print(f"  Error processing page: {e}")

    print("\n" + "="*50)
    print(f"Scan Complete. Found {len(broken_links)} broken links.")
    print("="*50)
    for source, link, status in broken_links:
        print(f"Source: {source}\n  -> Link: {link} (Status: {status})\n")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="WordPress Broken Link Checker")
    parser.add_argument("url", help="The URL of the website to scan")
    parser.add_argument("--limit", type=int, default=50, help="Max number of internal pages to crawl (default: 50)")

    args = parser.parse_args()

    crawl_site(args.url, args.limit)

Usage

# Scan a site (defaults to 50 pages)
python link_checker.py https://my-wordpress-site.com

# Scan deeper (100 pages)
python link_checker.py https://my-wordpress-site.com --limit 100

programming/python/python