ForageFacebook 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. foragefacebook-1.0.0/.github/FUNDING.yml +1 -0
  2. foragefacebook-1.0.0/.github/dependabot.yml +18 -0
  3. foragefacebook-1.0.0/.github/workflows/ci.yml +71 -0
  4. foragefacebook-1.0.0/.github/workflows/publish.yml +79 -0
  5. foragefacebook-1.0.0/.gitignore +62 -0
  6. foragefacebook-1.0.0/AGENTS.md +104 -0
  7. foragefacebook-1.0.0/CHANGELOG.md +38 -0
  8. foragefacebook-1.0.0/CLAUDE.md +126 -0
  9. foragefacebook-1.0.0/CONTRIBUTING.md +81 -0
  10. foragefacebook-1.0.0/LICENSE +373 -0
  11. foragefacebook-1.0.0/PKG-INFO +337 -0
  12. foragefacebook-1.0.0/README.md +302 -0
  13. foragefacebook-1.0.0/SECURITY.md +29 -0
  14. foragefacebook-1.0.0/pyproject.toml +60 -0
  15. foragefacebook-1.0.0/scripts/release.sh +61 -0
  16. foragefacebook-1.0.0/src/forage/__init__.py +3 -0
  17. foragefacebook-1.0.0/src/forage/auth.py +134 -0
  18. foragefacebook-1.0.0/src/forage/cli.py +310 -0
  19. foragefacebook-1.0.0/src/forage/exporter.py +222 -0
  20. foragefacebook-1.0.0/src/forage/models.py +74 -0
  21. foragefacebook-1.0.0/src/forage/parser.py +562 -0
  22. foragefacebook-1.0.0/src/forage/scraper.py +600 -0
  23. foragefacebook-1.0.0/tests/__init__.py +1 -0
  24. foragefacebook-1.0.0/tests/conftest.py +161 -0
  25. foragefacebook-1.0.0/tests/fixtures/comment_simple.html +7 -0
  26. foragefacebook-1.0.0/tests/fixtures/comment_with_replies.html +23 -0
  27. foragefacebook-1.0.0/tests/fixtures/post_long_content.html +13 -0
  28. foragefacebook-1.0.0/tests/fixtures/post_realistic.html +54 -0
  29. foragefacebook-1.0.0/tests/fixtures/post_simple.html +13 -0
  30. foragefacebook-1.0.0/tests/fixtures/post_sponsored.html +7 -0
  31. foragefacebook-1.0.0/tests/fixtures/post_with_emoji.html +12 -0
  32. foragefacebook-1.0.0/tests/fixtures/post_with_media.html +15 -0
  33. foragefacebook-1.0.0/tests/fixtures/post_with_see_more.html +12 -0
  34. foragefacebook-1.0.0/tests/test_cli.py +114 -0
  35. foragefacebook-1.0.0/tests/test_exporter.py +299 -0
  36. foragefacebook-1.0.0/tests/test_models.py +155 -0
  37. foragefacebook-1.0.0/tests/test_parser.py +255 -0
  38. foragefacebook-1.0.0/tests/test_scraper.py +222 -0
  39. foragefacebook-1.0.0/uv.lock +499 -0
@@ -0,0 +1 @@
1
+ github: [jwmoss]
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+ labels:
9
+ - "dependencies"
10
+
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ open-pull-requests-limit: 5
16
+ labels:
17
+ - "dependencies"
18
+ - "ci"
@@ -0,0 +1,71 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+ branches: [master, main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ run: uv python install ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --all-extras
27
+
28
+ - name: Install Playwright browsers
29
+ run: uv run playwright install chromium
30
+
31
+ - name: Run tests
32
+ run: uv run pytest tests/ -v --tb=short
33
+
34
+ type-check:
35
+ runs-on: ubuntu-latest
36
+
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+
40
+ - name: Install uv
41
+ uses: astral-sh/setup-uv@v4
42
+
43
+ - name: Set up Python
44
+ run: uv python install 3.12
45
+
46
+ - name: Install dependencies
47
+ run: uv sync --all-extras
48
+
49
+ - name: Run type checker
50
+ run: uv run ty check src/forage/
51
+
52
+ lint:
53
+ runs-on: ubuntu-latest
54
+
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - name: Install uv
59
+ uses: astral-sh/setup-uv@v4
60
+
61
+ - name: Set up Python
62
+ run: uv python install 3.12
63
+
64
+ - name: Install ruff
65
+ run: uv tool install ruff
66
+
67
+ - name: Run ruff check
68
+ run: ruff check src/forage/ tests/
69
+
70
+ - name: Run ruff format check
71
+ run: ruff format --check src/forage/ tests/
@@ -0,0 +1,79 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ test_pypi:
9
+ description: 'Publish to Test PyPI instead'
10
+ required: false
11
+ default: 'false'
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v4
22
+
23
+ - name: Set up Python
24
+ run: uv python install 3.12
25
+
26
+ - name: Build package
27
+ run: uv build
28
+
29
+ - name: Upload artifacts
30
+ uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish-test-pypi:
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ if: github.event_name == 'workflow_dispatch' && github.event.inputs.test_pypi == 'true'
39
+
40
+ environment:
41
+ name: test-pypi
42
+ url: https://test.pypi.org/project/forage/
43
+
44
+ permissions:
45
+ id-token: write
46
+
47
+ steps:
48
+ - name: Download artifacts
49
+ uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to Test PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ repository-url: https://test.pypi.org/legacy/
58
+
59
+ publish-pypi:
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ if: github.event_name == 'release'
63
+
64
+ environment:
65
+ name: pypi
66
+ url: https://pypi.org/project/forage/
67
+
68
+ permissions:
69
+ id-token: write
70
+
71
+ steps:
72
+ - name: Download artifacts
73
+ uses: actions/download-artifact@v4
74
+ with:
75
+ name: dist
76
+ path: dist/
77
+
78
+ - name: Publish to PyPI
79
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,62 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+
28
+ # IDE
29
+ .idea/
30
+ .vscode/
31
+ *.swp
32
+ *.swo
33
+
34
+ # Testing
35
+ .pytest_cache/
36
+ .coverage
37
+ htmlcov/
38
+
39
+ # Type checking
40
+ .ty_cache/
41
+
42
+ # OS
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ # Project specific
47
+ *.json
48
+ !package.json
49
+
50
+ # Session data (contains auth cookies - NEVER commit!)
51
+ session/
52
+ *.session
53
+ ~/.config/forage/
54
+
55
+ # Output files (may contain scraped data)
56
+ *.db
57
+ output/
58
+
59
+ # Environment variables (may contain secrets)
60
+ .env
61
+ .env.local
62
+ .env.*.local
@@ -0,0 +1,104 @@
1
+ # Agent Context for Forage
2
+
3
+ This document provides context for AI agents working on this codebase.
4
+
5
+ ## Project Overview
6
+
7
+ **Forage** is a Python CLI tool for scraping private Facebook groups. It uses Playwright for browser automation with saved session cookies for authentication.
8
+
9
+ ## Key Architecture Decisions
10
+
11
+ ### Browser Automation (Not API)
12
+ Facebook doesn't provide a public API for group data. We use Playwright to automate a real browser, which:
13
+ - Looks like legitimate user activity
14
+ - Works with private groups
15
+ - Handles JavaScript-heavy modern Facebook UI
16
+
17
+ ### Session-Based Auth
18
+ Users log in manually once via `forage login`, which saves browser cookies to `~/.config/forage/session/`. This avoids storing credentials and handles 2FA.
19
+
20
+ ### Anti-Detection
21
+ - Random delays between actions (not fixed timing)
22
+ - Random viewport sizes from common resolutions
23
+ - Real browser fingerprint (Playwright uses actual Chromium)
24
+
25
+ ## File Structure
26
+
27
+ ```
28
+ src/forage/
29
+ ├── cli.py # Entry point, Click commands
30
+ ├── auth.py # Login flow, session persistence
31
+ ├── scraper.py # Main scraping orchestration
32
+ ├── parser.py # HTML parsing (posts, comments)
33
+ └── models.py # Pydantic models for data
34
+ ```
35
+
36
+ ## Common Tasks
37
+
38
+ ### Adding a New CLI Flag
39
+ 1. Add to `ScrapeOptions` dataclass in `scraper.py`
40
+ 2. Add Click option in `cli.py` scrape command
41
+ 3. Pass to `ScrapeOptions` when constructing
42
+
43
+ ### Fixing Broken Selectors
44
+ Facebook frequently changes their HTML. When scraping breaks:
45
+ 1. Run with `--no-headless -v` to see the browser
46
+ 2. Use browser DevTools to inspect current HTML
47
+ 3. Update selectors in `parser.py` (look for `query_selector`)
48
+
49
+ ### Adding New Data Fields
50
+ 1. Add field to model in `models.py`
51
+ 2. Extract in appropriate `parse_*` function in `parser.py`
52
+ 3. JSON output updates automatically (Pydantic)
53
+
54
+ ## Known Fragile Areas
55
+
56
+ 1. **Post selectors** (`[data-pagelet^="FeedUnit"]`) - Facebook changes these
57
+ 2. **Timestamp parsing** - Many formats, relative times ("2h", "Yesterday")
58
+ 3. **Comment expansion** - "View more comments" button selectors change
59
+ 4. **Reaction counts** - Multiple ways reactions appear in HTML
60
+
61
+ ## Testing
62
+
63
+ ```bash
64
+ # Quick test (3 posts, no comments)
65
+ uv run forage -v scrape GROUP_SLUG --limit 3 --skip-comments
66
+
67
+ # Debug mode (watch browser)
68
+ uv run forage -v scrape GROUP_SLUG --limit 1 --no-headless
69
+
70
+ # Type check
71
+ uv run ty check src/
72
+ ```
73
+
74
+ ## Dependencies
75
+
76
+ - **click**: CLI framework
77
+ - **playwright**: Browser automation
78
+ - **pydantic**: Data validation and JSON serialization
79
+ - **rich**: Terminal output formatting
80
+
81
+ ## Anti-Detection Strategy
82
+
83
+ The scraper uses several techniques to appear human:
84
+
85
+ 1. **Random delays**: `human_delay()` adds variance to wait times
86
+ 2. **Viewport randomization**: Picks from common screen sizes
87
+ 3. **Real browser**: Playwright runs actual Chromium, not headless-only
88
+ 4. **Session persistence**: Uses real logged-in session, not API tokens
89
+
90
+ ## Rate Limiting
91
+
92
+ Default delay is 2 seconds between actions. For large scrapes:
93
+ - Use `--delay 5.0` or higher
94
+ - Consider running in batches with breaks
95
+ - Facebook may temporarily block if too aggressive
96
+
97
+ ## Session Management
98
+
99
+ Sessions are stored in `~/.config/forage/session/storage_state.json`. This includes:
100
+ - Cookies
101
+ - Local storage
102
+ - Session storage
103
+
104
+ Sessions typically expire after ~30 days or if Facebook detects unusual activity.
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2025-01-08
11
+
12
+ ### Added
13
+
14
+ - Initial stable release
15
+ - `forage login` command for interactive Facebook authentication
16
+ - `forage scrape` command for scraping posts, comments, and reactions
17
+ - JSON output format (default)
18
+ - SQLite export format (`-f sqlite`)
19
+ - CSV export format (`-f csv`)
20
+ - Date range filtering (`--days`, `--since`, `--until`)
21
+ - Comment filtering (`--min-reactions`, `--top-comments`, `--skip-comments`)
22
+ - Rate limiting with configurable delay (`--delay`)
23
+ - Retry logic with exponential backoff for network errors
24
+ - Anti-detection features (random delays, viewport rotation)
25
+ - Stdin support for group input (`echo "group" | forage scrape -`)
26
+ - GitHub Actions CI with tests, type checking, and linting
27
+ - PyPI publishing workflow
28
+ - Comprehensive test suite (96 tests)
29
+ - Documentation (README, CONTRIBUTING, SECURITY, AGENTS, CLAUDE)
30
+
31
+ ### Security
32
+
33
+ - Session data stored securely in `~/.config/forage/session/`
34
+ - Sensitive files excluded via `.gitignore`
35
+ - Security guidelines in SECURITY.md
36
+
37
+ [Unreleased]: https://github.com/jwmoss/forage/compare/v1.0.0...HEAD
38
+ [1.0.0]: https://github.com/jwmoss/forage/releases/tag/v1.0.0
@@ -0,0 +1,126 @@
1
+ # Claude Context for Forage
2
+
3
+ This file provides context for Claude (or other AI assistants) when working on this project.
4
+
5
+ ## Project Purpose
6
+
7
+ Forage is a personal tool for scraping private Facebook groups to analyze community discussions. The owner uses it to:
8
+ - Understand what's popular in their local community
9
+ - Identify trends and common topics
10
+ - Gather data for potential app ideas
11
+
12
+ ## Technology Choices
13
+
14
+ | Choice | Why |
15
+ |--------|-----|
16
+ | Python | Fast iteration, good scraping ecosystem |
17
+ | Playwright | Best browser automation, handles modern SPAs |
18
+ | Click | Clean CLI framework |
19
+ | Pydantic | Type-safe data models, automatic JSON |
20
+ | uv | Fast Python package management |
21
+ | ty | Astral's type checker (fast, modern) |
22
+
23
+ ## Development Workflow
24
+
25
+ ```bash
26
+ # Always use uv, not python directly
27
+ uv sync # Install deps
28
+ uv run forage --help # Run CLI
29
+ uv run ty check src/ # Type check
30
+ uv run pytest # Tests
31
+ ```
32
+
33
+ ## Code Style
34
+
35
+ - Type hints on all functions
36
+ - Docstrings for public functions
37
+ - Use `from __future__ import annotations` for forward refs
38
+ - Prefer explicit over implicit
39
+ - Handle exceptions gracefully (scraping is brittle)
40
+
41
+ ## Key Implementation Details
42
+
43
+ ### The Scraping Flow
44
+
45
+ 1. `cli.py:scrape()` - Entry point, builds options
46
+ 2. `scraper.py:scrape_group()` - Main loop
47
+ 3. For each post in feed:
48
+ - `parser.py:parse_modern_post()` - Extract post data
49
+ - If comments enabled: `scraper.py:scrape_post_comments()` or `scrape_comments_from_post_page()`
50
+ 4. Apply filters (min_reactions, top_comments)
51
+ 5. Return `ScrapeResult` (serializes to JSON)
52
+
53
+ ### Authentication Flow
54
+
55
+ 1. `cli.py:login()` - Opens browser
56
+ 2. `auth.py:login()` - Waits for user to log in
57
+ 3. Saves `storage_state.json` (cookies + storage)
58
+ 4. Future runs load this state into browser context
59
+
60
+ ### HTML Parsing Strategy
61
+
62
+ Facebook's React UI has deeply nested, frequently-changing HTML. Strategy:
63
+ 1. Find stable containers (`[data-pagelet^="FeedUnit"]`)
64
+ 2. Extract text content
65
+ 3. Use heuristics to identify author, content, timestamps
66
+ 4. Fall back gracefully when selectors fail
67
+
68
+ ## Common Issues & Solutions
69
+
70
+ ### "Session expired" errors
71
+ - Session cookies expire (~30 days)
72
+ - Run `forage login` again
73
+ - CLI auto-prompts for re-login
74
+
75
+ ### Scraper finds 0 posts
76
+ - Facebook changed their HTML structure
77
+ - Run with `--no-headless -v` to debug
78
+ - Check console for selector matches
79
+ - Update selectors in `parser.py`
80
+
81
+ ### Rate limiting / blocked
82
+ - Increase `--delay` (try 5-10 seconds)
83
+ - Take breaks between large scrapes
84
+ - May need to wait 24h if blocked
85
+
86
+ ### Comments not loading
87
+ - Facebook loads comments lazily
88
+ - The scraper tries to click "View more comments"
89
+ - May need to navigate to post page
90
+
91
+ ## Future Improvements
92
+
93
+ - [ ] Async scraping for performance
94
+ - [ ] Better timestamp parsing
95
+ - [ ] Individual reaction types (like/love/haha)
96
+ - [ ] Media URL extraction
97
+ - [ ] Export to CSV/SQLite
98
+ - [ ] Retry logic with exponential backoff
99
+
100
+ ## Testing Tips
101
+
102
+ When making changes:
103
+ 1. Test with a small limit first: `--limit 3`
104
+ 2. Use `--skip-comments` for faster iteration
105
+ 3. Use `--no-headless -v` to see what's happening
106
+ 4. Check the JSON output structure is correct
107
+
108
+ ## Important Files
109
+
110
+ | File | Purpose |
111
+ |------|---------|
112
+ | `cli.py` | All CLI commands and options |
113
+ | `scraper.py` | Scraping logic, browser control |
114
+ | `parser.py` | HTML parsing, data extraction |
115
+ | `models.py` | Pydantic models (Post, Comment, etc.) |
116
+ | `auth.py` | Session management |
117
+
118
+ ## Fragile Code Warnings
119
+
120
+ These areas break frequently due to Facebook changes:
121
+
122
+ 1. **`parser.py:parse_modern_post()`** - Post extraction heuristics
123
+ 2. **`scraper.py` selectors** - `[data-pagelet^="FeedUnit"]` etc.
124
+ 3. **Timestamp parsing** - Many edge cases
125
+
126
+ When something breaks, check these first.
@@ -0,0 +1,81 @@
1
+ # Contributing to Forage
2
+
3
+ Thank you for your interest in contributing to Forage!
4
+
5
+ ## Development Setup
6
+
7
+ 1. Clone the repository:
8
+ ```bash
9
+ git clone https://github.com/jwmoss/forage.git
10
+ cd forage
11
+ ```
12
+
13
+ 2. Install dependencies using uv:
14
+ ```bash
15
+ uv sync --all-extras
16
+ ```
17
+
18
+ 3. Install Playwright browsers:
19
+ ```bash
20
+ uv run playwright install chromium
21
+ ```
22
+
23
+ 4. Run tests:
24
+ ```bash
25
+ uv run pytest tests/ -v
26
+ ```
27
+
28
+ 5. Run type checker:
29
+ ```bash
30
+ uv run ty check src/forage/
31
+ ```
32
+
33
+ ## Code Style
34
+
35
+ - Use [ruff](https://docs.astral.sh/ruff/) for linting and formatting
36
+ - Follow PEP 8 style guidelines
37
+ - Add type hints to all function signatures
38
+ - Write docstrings for public functions
39
+
40
+ ## Commit Messages
41
+
42
+ We use [Conventional Commits](https://www.conventionalcommits.org/):
43
+
44
+ - `feat:` new feature
45
+ - `fix:` bug fix
46
+ - `docs:` documentation changes
47
+ - `test:` adding or updating tests
48
+ - `refactor:` code refactoring
49
+ - `chore:` maintenance tasks
50
+
51
+ Example: `feat: add SQLite export option`
52
+
53
+ ## Pull Request Process
54
+
55
+ 1. Fork the repository
56
+ 2. Create a feature branch (`git checkout -b feat/my-feature`)
57
+ 3. Make your changes
58
+ 4. Ensure tests pass (`uv run pytest tests/`)
59
+ 5. Ensure type checking passes (`uv run ty check src/forage/`)
60
+ 6. Commit with a descriptive message
61
+ 7. Push and open a pull request
62
+
63
+ ## Testing
64
+
65
+ - Write tests for new functionality
66
+ - Use pytest fixtures for common test data
67
+ - Mock external dependencies (Playwright, network)
68
+ - Aim for good coverage of edge cases
69
+
70
+ ## Reporting Issues
71
+
72
+ When reporting bugs, please include:
73
+ - Python version (`python --version`)
74
+ - Operating system
75
+ - Steps to reproduce
76
+ - Expected vs actual behavior
77
+ - Any error messages
78
+
79
+ ## Questions?
80
+
81
+ Feel free to open a GitHub issue for questions or discussions.