chore: import upstream snapshot with attribution
Tests / tests (map[TOXENV:py310], macos-latest, 3.10) (push) Has been cancelled
Tests / tests (map[TOXENV:py311], macos-latest, 3.11) (push) Has been cancelled
Tests / tests (map[TOXENV:py312], macos-latest, 3.12) (push) Has been cancelled
Tests / tests (map[TOXENV:py313], macos-latest, 3.13) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:01:50 +08:00
commit 247153575d
244 changed files with 44853 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
github: D4Vinci
buy_me_a_coffee: d4vinci
ko_fi: d4vinci
+82
View File
@@ -0,0 +1,82 @@
name: Bug report
description: Create a bug report to help us address errors in the repository
labels: [bug]
body:
- type: checkboxes
attributes:
label: Have you searched if there an existing issue for this?
description: Please search [existing issues](https://github.com/D4Vinci/Scrapling/labels/bug).
options:
- label: I have searched the existing issues
required: true
- type: input
attributes:
label: "Python version (python --version)"
placeholder: "Python 3.8"
validations:
required: true
- type: input
attributes:
label: "Scrapling version (scrapling.__version__)"
placeholder: "0.1"
validations:
required: true
- type: textarea
attributes:
label: "Dependencies version (pip3 freeze)"
description: >
This is the output of the command `pip3 freeze --all`. Note that the
actual output might be different as compared to the placeholder text.
placeholder: |
cssselect==1.2.0
lxml==5.3.0
orjson==3.10.7
...
validations:
required: true
- type: input
attributes:
label: "What's your operating system?"
placeholder: "Windows 10"
validations:
required: true
- type: dropdown
attributes:
label: 'Are you using a separate virtual environment?'
description: "Please pay attention to this question"
options:
- 'No'
- 'Yes'
default: 0
validations:
required: true
- type: textarea
attributes:
label: "Expected behavior"
description: "Describe the behavior you expect. May include images or videos."
validations:
required: true
- type: textarea
attributes:
label: "Actual behavior"
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
@@ -0,0 +1,19 @@
name: Feature request
description: Suggest features, propose improvements, discuss new ideas.
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Have you searched if there an existing feature request for this?
description: Please search [existing requests](https://github.com/D4Vinci/Scrapling/labels/enhancement).
options:
- label: I have searched the existing requests
required: true
- type: textarea
attributes:
label: "Feature description"
description: >
This could include new topics or improving any existing features/implementations.
validations:
required: true
+19
View File
@@ -0,0 +1,19 @@
name: Other
description: Use this for any other issues. PLEASE provide as much information as possible.
labels: ["awaiting triage"]
body:
- type: textarea
id: issuedescription
attributes:
label: What would you like to share?
description: Provide a clear and concise explanation of your issue.
validations:
required: true
- type: textarea
id: extrainfo
attributes:
label: Additional information
description: Is there anything else we should know about this issue?
validations:
required: false
+40
View File
@@ -0,0 +1,40 @@
name: Documentation issue
description: Report incorrect, unclear, or missing documentation.
labels: [documentation]
body:
- type: checkboxes
attributes:
label: Have you searched if there an existing issue for this?
description: Please search [existing issues](https://github.com/D4Vinci/Scrapling/labels/documentation).
options:
- label: I have searched the existing issues
required: true
- type: input
attributes:
label: "Page URL"
description: "Link to the documentation page with the issue."
placeholder: "https://scrapling.readthedocs.io/en/latest/..."
validations:
required: true
- type: dropdown
attributes:
label: "Type of issue"
options:
- Incorrect information
- Unclear or confusing
- Missing information
- Typo or formatting
- Broken link
- Other
default: 0
validations:
required: true
- type: textarea
attributes:
label: "Description"
description: "Describe what's wrong and what you expected to find."
validations:
required: true
+10
View File
@@ -0,0 +1,10 @@
blank_issues_enabled: false
contact_links:
- name: Discussions
url: https://github.com/D4Vinci/Scrapling/discussions
about: >
The "Discussions" forum is where you want to start. 💖
- name: Ask on our discord server
url: https://discord.gg/EMgGbDceNQ
about: >
Our community chat forum.
+51
View File
@@ -0,0 +1,51 @@
<!--
You are amazing! Thanks for contributing to Scrapling!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Proposed change
<!--
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
If it fixes a bug or resolves a feature request, be sure to link to that issue in the additional information section.
-->
### Type of change:
<!--
What type of change does your PR introduce to Scrapling?
NOTE: Please, check at least 1 box!
If your PR requires multiple boxes to be checked, you'll most likely need to
split it into multiple PRs. This makes things easier and faster to code review.
-->
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Add or change doctests? -- Note: Please avoid changing both code and tests in a single pull request.
- [ ] Documentation change?
### Additional information
<!--
Details are important and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable.
-->
- This PR fixes or closes an issue: fixes #
- This PR is related to an issue: #
- Link to documentation pull request: **
### Checklist:
* [ ] I have read [CONTRIBUTING.md](https://github.com/D4Vinci/Scrapling/blob/main/CONTRIBUTING.md).
* [ ] This pull request is all my own work -- I have not plagiarized.
* [ ] I know that pull requests will not be merged if they fail the automated tests.
* [ ] All new Python files are placed inside an existing directory.
* [ ] All filenames are in all lowercase characters with no spaces or dashes.
* [ ] All functions and variable names follow Python naming conventions.
* [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html).
* [ ] All functions have doc-strings.
+190
View File
@@ -0,0 +1,190 @@
name: Code Quality
on:
push:
branches:
- main
- dev
paths-ignore:
- '*.md'
- '**/*.md'
- 'docs/**'
- 'images/**'
- '.github/**'
- 'agent-skill/**'
- '!.github/workflows/code-quality.yml' # Always run when this workflow changes
pull_request:
branches:
- main
- dev
paths-ignore:
- '*.md'
- '**/*.md'
- 'docs/**'
- 'images/**'
- '.github/**'
- 'agent-skill/**'
- '*.yml'
- '*.yaml'
- 'ruff.toml'
workflow_dispatch: # Allow manual triggering
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
code-quality:
name: Code Quality Checks
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # For PR annotations
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # Full history for better analysis
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install bandit[toml] ruff vermin mypy pyright
pip install -e ".[all]"
pip install lxml-stubs scrapy
- name: Run Bandit (Security Linter)
id: bandit
continue-on-error: true
run: |
echo "::group::Bandit - Security Linter"
bandit -r -c .bandit.yml scrapling/ -f json -o bandit-report.json
bandit -r -c .bandit.yml scrapling/
echo "::endgroup::"
- name: Run Ruff Linter
id: ruff-lint
continue-on-error: true
run: |
echo "::group::Ruff - Linter"
ruff check scrapling/ --output-format=github
echo "::endgroup::"
- name: Run Ruff Formatter Check
id: ruff-format
continue-on-error: true
run: |
echo "::group::Ruff - Formatter Check"
ruff format --check scrapling/ --diff
echo "::endgroup::"
- name: Run Vermin (Python Version Compatibility)
id: vermin
continue-on-error: true
run: |
echo "::group::Vermin - Python 3.10+ Compatibility Check"
vermin -t=3.10- --violations --eval-annotations --no-tips scrapling/
echo "::endgroup::"
- name: Run Mypy (Static Type Checker)
id: mypy
continue-on-error: true
run: |
echo "::group::Mypy - Static Type Checker"
mypy scrapling/
echo "::endgroup::"
- name: Run Pyright (Static Type Checker)
id: pyright
continue-on-error: true
run: |
echo "::group::Pyright - Static Type Checker"
pyright scrapling/
echo "::endgroup::"
- name: Check results and create summary
if: always()
run: |
echo "# Code Quality Check Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Initialize status
all_passed=true
# Check Bandit
if [ "${{ steps.bandit.outcome }}" == "success" ]; then
echo "✅ **Bandit (Security)**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Bandit (Security)**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
# Check Ruff Linter
if [ "${{ steps.ruff-lint.outcome }}" == "success" ]; then
echo "✅ **Ruff Linter**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Ruff Linter**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
# Check Ruff Formatter
if [ "${{ steps.ruff-format.outcome }}" == "success" ]; then
echo "✅ **Ruff Formatter**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Ruff Formatter**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
# Check Vermin
if [ "${{ steps.vermin.outcome }}" == "success" ]; then
echo "✅ **Vermin (Python 3.10+)**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Vermin (Python 3.10+)**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
# Check Mypy
if [ "${{ steps.mypy.outcome }}" == "success" ]; then
echo "✅ **Mypy (Type Checker)**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Mypy (Type Checker)**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
# Check Pyright
if [ "${{ steps.pyright.outcome }}" == "success" ]; then
echo "✅ **Pyright (Type Checker)**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Pyright (Type Checker)**: Failed" >> $GITHUB_STEP_SUMMARY
all_passed=false
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$all_passed" == "true" ]; then
echo "### 🎉 All checks passed!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Your code meets all quality standards." >> $GITHUB_STEP_SUMMARY
else
echo "### ⚠️ Some checks failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Please review the errors above and fix them." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tip**: Run \`pre-commit run --all-files\` locally to catch these issues before pushing." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Upload Bandit report
if: always() && steps.bandit.outcome != 'skipped'
uses: actions/upload-artifact@v6
with:
name: bandit-security-report
path: bandit-report.json
retention-days: 30
+86
View File
@@ -0,0 +1,86 @@
name: Build and Push Docker Image
on:
pull_request:
types: [closed]
branches:
- main
workflow_dispatch:
inputs:
tag:
description: 'Docker image tag'
required: true
default: 'latest'
env:
DOCKERHUB_IMAGE: pyd4vinci/scrapling
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/scrapling
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.CONTAINER_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_IMAGE }}
${{ env.GHCR_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=Scrapling
org.opencontainers.image.description=An undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be!
org.opencontainers.image.vendor=D4Vinci
org.opencontainers.image.licenses=BSD
org.opencontainers.image.url=https://scrapling.readthedocs.io/en/latest/
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.documentation=https://scrapling.readthedocs.io/en/latest/
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: Image digest
run: echo ${{ steps.build.outputs.digest }}
+74
View File
@@ -0,0 +1,74 @@
name: Create Release and Publish to PyPI
# Creates a GitHub release when a PR is merged to main (using PR title as version and body as release notes), then publishes to PyPI.
on:
pull_request:
types: [closed]
branches:
- main
jobs:
create-release-and-publish:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment:
name: PyPI
url: https://pypi.org/p/scrapling
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get PR title
id: pr_title
run: echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
- name: Save PR body to file
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
fs.writeFileSync('pr_body.md', context.payload.pull_request.body || '');
- name: Extract version
id: extract_version
run: |
PR_TITLE="${{ steps.pr_title.outputs.title }}"
if [[ $PR_TITLE =~ ^v ]]; then
echo "version=$PR_TITLE" >> $GITHUB_OUTPUT
echo "Valid version format found in PR title: $PR_TITLE"
else
echo "Error: PR title '$PR_TITLE' must start with 'v' (e.g., 'v1.0.0') to create a release."
exit 1
fi
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.extract_version.outputs.version }}
name: Release ${{ steps.extract_version.outputs.version }}
body_path: pr_body.md
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.12
- name: Upgrade pip
run: python3 -m pip install --upgrade pip
- name: Install build
run: python3 -m pip install --upgrade build twine setuptools
- name: Build a binary wheel and a source tarball
run: python3 -m build --sdist --wheel --outdir dist/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+124
View File
@@ -0,0 +1,124 @@
name: Tests
on:
push:
branches:
- main
- dev
paths-ignore:
- '*.md'
- '**/*.md'
- 'docs/**'
- 'images/**'
- '.github/**'
- 'agent-skill/**'
- '*.yml'
- '*.yaml'
- 'ruff.toml'
pull_request:
branches:
- main
- dev
paths-ignore:
- '*.md'
- '**/*.md'
- 'docs/**'
- 'images/**'
- '.github/**'
- 'agent-skill/**'
- '*.yml'
- '*.yaml'
- 'ruff.toml'
concurrency:
group: ${{github.workflow}}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
timeout-minutes: 60
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- python-version: "3.10"
os: macos-latest
env:
TOXENV: py310
- python-version: "3.11"
os: macos-latest
env:
TOXENV: py311
- python-version: "3.12"
os: macos-latest
env:
TOXENV: py312
- python-version: "3.13"
os: macos-latest
env:
TOXENV: py313
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml
tox.ini
- name: Install all browsers dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install playwright==1.61.0 patchright==1.61.1
- name: Get Playwright version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(python3 -c "import importlib.metadata; print(importlib.metadata.version('playwright'))")
echo "version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
echo "Playwright version: $PLAYWRIGHT_VERSION"
- name: Retrieve Playwright browsers from cache if any
id: playwright-cache
uses: actions/cache@v5
with:
path: |
~/.cache/ms-playwright
~/Library/Caches/ms-playwright
~/.ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}-v1
restore-keys: |
${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}-
${{ runner.os }}-playwright-
- name: Install Playwright browsers
run: |
echo "Cache hit: ${{ steps.playwright-cache.outputs.cache-hit }}"
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" != "true" ]; then
python3 -m playwright install chromium
else
echo "Skipping install - using cached Playwright browsers"
fi
python3 -m playwright install-deps chromium
# Cache tox environments
- name: Cache tox environments
uses: actions/cache@v5
with:
path: .tox
# Include python version and os in the cache key
key: tox-v1-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('/Users/runner/work/Scrapling/pyproject.toml') }}
restore-keys: |
tox-v1-${{ runner.os }}-py${{ matrix.python-version }}-
tox-v1-${{ runner.os }}-
- name: Install tox
run: pip install -U tox
- name: Run tests
env: ${{ matrix.env }}
run: tox