A robust, configurable Python tool for scraping homebrew recipes from a forum, with support for pagination, retries, structured output, and logging.
- Overview
- Features
- Requirements
- Installation
- Usage
- Command-Line Arguments
- Output Format
- Customization
- Error Handling & Logging
- Contributing
This scraper navigates a homebrewing forum’s recipe section (including paginated pages), extracts structured data (title, author, date, ingredients, instructions) from each post, and saves the results to a JSON file. It employs retries, respectful delays, and a realistic User-Agent string to maximize reliability and minimize the chance of being blocked.
- Command-Line Interface Configure start URL, output file path, and verbosity via arguments.
- Session with Custom Headers
Uses a persistent
requests.Sessionwith a modern User-Agent. - Automatic Retries Retries failed HTTP requests up to three times, with delays between attempts.
- Pagination Support
Detects “next page” links (via
rel="next",.next-pageCSS class, or link text “Next/More”) and follows them automatically. - Structured Data Extraction Parses ingredients and instructions into lists of lines (instead of raw blobs of text).
- Logging Detailed logs for each page scraped, including number of posts found, retry attempts, and parsing errors.
- Respectful Delay
A short
time.sleep(1)between pages to avoid hammering the server. - Graceful Error Handling Skips any post missing mandatory fields rather than crashing. Catches file-write errors when exporting JSON.
- UTF-8 Encoding Ensures proper handling of non-ASCII characters when saving JSON.
-
Python 3.7 or higher
-
The following Python libraries:
requestsbeautifulsoup4
You can install dependencies with:
pip install requests beautifulsoup4-
Clone the repository (or copy the script file):
git clone https://github.com/yourusername/homebrew-recipe-scraper.git cd homebrew-recipe-scraper -
Make sure dependencies are installed:
pip install requests beautifulsoup4
-
Ensure the main script is executable (if using Unix/macOS):
chmod +x scrape_recipes.py
python scrape_recipes.py <START_URL> [OPTIONS]-
<START_URL>The URL of the first page listing forum recipes (e.g.,https://www.example-homebrewing-forum.com/recipes). -
Options
-o, --output <FILE>Path to the output JSON file. Defaults tohomebrew_recipes.json.-v, --verboseEnable debug-level logging for detailed trace information.
Example:
python scrape_recipes.py https://www.example-homebrewing-forum.com/recipes \
-o my_recipes.json \
--verboseThis command:
- Starts scraping at
https://www.example-homebrewing-forum.com/recipes. - Outputs the results to
my_recipes.json. - Prints debug logs to the console.
| Argument | Description | Default |
|---|---|---|
<START_URL> |
(Positional) Starting URL of the recipe listing page. | N/A |
-o, --output <FILE> |
Path to the JSON file where scraped recipes will be saved. | homebrew_recipes.json |
-v, --verbose |
Activate debug logging (shows detailed internal steps and retry attempts). | Disabled |
The script produces a JSON array containing one object per recipe. Each recipe object has the following structure:
- title: The recipe’s title (string).
- author: The forum username who posted the recipe (string).
- date: The date string as shown on the post (string).
- ingredients: A list of ingredient lines (array of strings).
- instructions: A list of instruction lines (array of strings).
-
Adjusting CSS Selectors If your forum’s markup uses different classes or HTML tags, update the
extract_recipe(post)function accordingly. For example:title_tag = post.find("h2", class_="post-title")
or
ingr_list_items = ingr_tag.find_all("li") ingredients = [li.get_text(strip=True) for li in ingr_list_items]
-
Handling Additional Fields To scrape images, ratings, or tags:
-
Locate the appropriate tag(s) in
extract_recipe(). -
Add new keys to the returned dictionary:
recipe["rating"] = post.find("span", class_="rating").get_text(strip=True) recipe["image_url"] = post.find("img", class_="recipe-image")["src"]
-
-
Modifying Pagination Logic
- If your forum uses a different pattern (e.g.,
?page=2query parameter), adaptfind_next_page()to detect and construct the “next page” URL accordingly. - For JavaScript-driven pagination, consider using a headless browser (e.g., Selenium) or an API (if available).
- If your forum uses a different pattern (e.g.,
-
Export Formats
- To output CSV instead of JSON, import Python’s
csvmodule and write rows accordingly in themain()function. - To insert directly into a database, replace the JSON-dump block with your database client logic.
- To output CSV instead of JSON, import Python’s
-
Network/Timeout Errors
- The
fetch_url()function retries up to 3 times (with a 3-second delay) before giving up. - If a page consistently fails, the scraper logs an error and moves on to stop the loop.
- The
-
Missing Fields
- If a mandatory field (title, author, date, ingredients, or instructions) is missing,
extract_recipe()returnsNoneand the post is skipped (no crash).
- If a mandatory field (title, author, date, ingredients, or instructions) is missing,
-
Logging Levels
- INFO (default): Logs each page URL, number of posts found, and final summary.
- DEBUG (when
-v/--verboseis used): Detailed traces of HTTP attempts, retries, parsing decisions, and any missing-field warnings.
-
Output File Errors
- If writing to the JSON file fails (e.g., due to permissions), an error is logged instead of a silent failure.
- Fork this repository and create a new branch for your feature or bugfix.
- Write clear, concise commit messages describing your changes.
- Update the
README.mdor documentation if you add new functionality. - Submit a pull request with your proposed changes.
Please adhere to the existing coding style—particularly around logging, function docstrings, and error handling.
[ { "title": "Example Recipe Title", "author": "Username123", "date": "2025-05-29", "ingredients": [ "10 lbs Pale Malt", "1 lb Crystal Malt 60L", "1 oz Cascade Hops @ 60 min", "1 oz Cascade Hops @ 15 min" ], "instructions": [ "Mash grains at 152°F for 60 minutes.", "Sparge with 170°F water.", "Boil for 60 minutes, adding hops as scheduled.", "Chill wort to 68°F and pitch yeast." ] }, ... ]