diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index de22a2783..d9e4a2831 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 2.1.0
+current_version = 2.5.0
commit = True
tag = True
tag_name = {new_version}
diff --git a/.flake8 b/.flake8
new file mode 100644
index 000000000..1c503fb0b
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,19 @@
+[flake8]
+
+max-line-length = 119
+ignore = W503
+
+exclude =
+# Exclude files that are meant to provide top-level imports
+# E402: Module level import not at top of file
+# F401: Module imported but unused
+ scrapy/__init__.py E402
+ scrapy/core/downloader/handlers/http.py F401
+ scrapy/http/__init__.py F401
+ scrapy/linkextractors/__init__.py E402 F401
+ scrapy/selector/__init__.py F401
+ scrapy/spiders/__init__.py E402 F401
+
+ # Issues pending a review:
+ scrapy/utils/url.py F403 F405
+ tests/test_loader.py E741
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..dfbdf4208
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+tests/sample_data/** binary
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644
index 000000000..6bdfcb5dc
--- /dev/null
+++ b/.github/workflows/checks.yml
@@ -0,0 +1,45 @@
+name: Checks
+on: [push, pull_request]
+
+jobs:
+ checks:
+ runs-on: ubuntu-18.04
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - python-version: 3.9
+ env:
+ TOXENV: security
+ - python-version: 3.9
+ env:
+ TOXENV: flake8
+ # Pylint requires installing reppy, which does not support Python 3.9
+ # https://github.com/seomoz/reppy/issues/122
+ - python-version: 3.8
+ env:
+ TOXENV: pylint
+ TOX_PIP_VERSION: 20.3.3
+ - python-version: 3.9
+ env:
+ TOXENV: typing
+ - python-version: 3.8 # Keep in sync with .readthedocs.yml
+ env:
+ TOXENV: docs
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Run check
+ env: ${{ matrix.env }}
+ run: |
+ if [[ ! -z "$TOX_PIP_VERSION" ]]; then
+ pip install tox-pip-version
+ fi
+ pip install -U tox
+ tox
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 000000000..b48066ea4
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,31 @@
+name: Publish
+on: [push]
+
+jobs:
+ publish:
+ runs-on: ubuntu-18.04
+ if: startsWith(github.event.ref, 'refs/tags/')
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+
+ - name: Check Tag
+ id: check-release-tag
+ run: |
+ if [[ ${{ github.event.ref }} =~ ^refs/tags/[0-9]+[.][0-9]+[.][0-9]+(rc[0-9]+|[.]dev[0-9]+)?$ ]]; then
+ echo ::set-output name=release_tag::true
+ fi
+
+ - name: Publish to PyPI
+ if: steps.check-release-tag.outputs.release_tag == 'true'
+ run: |
+ pip install --upgrade setuptools wheel twine
+ python setup.py sdist bdist_wheel
+ export TWINE_USERNAME=__token__
+ export TWINE_PASSWORD=${{ secrets.PYPI_TOKEN }}
+ twine upload dist/*
diff --git a/.github/workflows/tests-macos.yml b/.github/workflows/tests-macos.yml
new file mode 100644
index 000000000..095ca1013
--- /dev/null
+++ b/.github/workflows/tests-macos.yml
@@ -0,0 +1,26 @@
+name: macOS
+on: [push, pull_request]
+
+jobs:
+ tests:
+ runs-on: macos-10.15
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: [3.6, 3.7, 3.8, 3.9]
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Run tests
+ run: |
+ pip install -U tox
+ tox -e py
+
+ - name: Upload coverage report
+ run: bash <(curl -s https://codecov.io/bash)
diff --git a/.github/workflows/tests-ubuntu.yml b/.github/workflows/tests-ubuntu.yml
new file mode 100644
index 000000000..57188bd63
--- /dev/null
+++ b/.github/workflows/tests-ubuntu.yml
@@ -0,0 +1,80 @@
+name: Ubuntu
+on: [push, pull_request]
+
+jobs:
+ tests:
+ runs-on: ubuntu-18.04
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - python-version: 3.7
+ env:
+ TOXENV: py
+ - python-version: 3.8
+ env:
+ TOXENV: py
+ - python-version: 3.9
+ env:
+ TOXENV: py
+ - python-version: pypy3
+ env:
+ TOXENV: pypy3
+ PYPY_VERSION: 3.6-v7.3.1
+
+ # pinned deps
+ - python-version: 3.6.12
+ env:
+ TOXENV: pinned
+ - python-version: 3.6.12
+ env:
+ TOXENV: asyncio-pinned
+ - python-version: pypy3
+ env:
+ TOXENV: pypy3-pinned
+ PYPY_VERSION: 3.6-v7.2.0
+
+ # extras
+ # extra-deps includes reppy, which does not support Python 3.9
+ # https://github.com/seomoz/reppy/issues/122
+ - python-version: 3.8
+ env:
+ TOXENV: extra-deps
+ TOX_PIP_VERSION: 20.3.3
+ - python-version: 3.9
+ env:
+ TOXENV: asyncio
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install system libraries
+ if: matrix.python-version == 'pypy3' || contains(matrix.env.TOXENV, 'pinned')
+ run: |
+ sudo apt-get update
+ # libxml2 2.9.12 from ondrej/php PPA breaks lxml so we pin it to the bionic-updates repo version
+ sudo apt-get install libxml2-dev/bionic-updates libxslt-dev
+
+ - name: Run tests
+ env: ${{ matrix.env }}
+ run: |
+ if [[ ! -z "$PYPY_VERSION" ]]; then
+ export PYPY_VERSION="pypy$PYPY_VERSION-linux64"
+ wget "https://downloads.python.org/pypy/${PYPY_VERSION}.tar.bz2"
+ tar -jxf ${PYPY_VERSION}.tar.bz2
+ $PYPY_VERSION/bin/pypy3 -m venv "$HOME/virtualenvs/$PYPY_VERSION"
+ source "$HOME/virtualenvs/$PYPY_VERSION/bin/activate"
+ fi
+ if [[ ! -z "$TOX_PIP_VERSION" ]]; then
+ pip install tox-pip-version
+ fi
+ pip install -U tox
+ tox
+
+ - name: Upload coverage report
+ run: bash <(curl -s https://codecov.io/bash)
diff --git a/.github/workflows/tests-windows.yml b/.github/workflows/tests-windows.yml
new file mode 100644
index 000000000..30fda33e8
--- /dev/null
+++ b/.github/workflows/tests-windows.yml
@@ -0,0 +1,37 @@
+name: Windows
+on: [push, pull_request]
+
+jobs:
+ tests:
+ runs-on: windows-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - python-version: 3.6
+ env:
+ TOXENV: windows-pinned
+ - python-version: 3.7
+ env:
+ TOXENV: py
+ - python-version: 3.8
+ env:
+ TOXENV: py
+ # https://twistedmatrix.com/trac/ticket/9990
+ #- python-version: 3.9
+ #env:
+ #TOXENV: py
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Run tests
+ env: ${{ matrix.env }}
+ run: |
+ pip install -U tox
+ tox
diff --git a/.gitignore b/.gitignore
index ff6e2ea65..d77d24624 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,12 @@ htmlcov/
.coverage
.pytest_cache/
.coverage.*
+coverage.*
+test-output.*
.cache/
+.mypy_cache/
+/tests/keys/localhost.crt
+/tests/keys/localhost.key
# Windows
Thumbs.db
diff --git a/.readthedocs.yml b/.readthedocs.yml
index e4d3f02cc..80a1cd036 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -3,10 +3,14 @@ formats: all
sphinx:
configuration: docs/conf.py
fail_on_warning: true
+
+build:
+ image: latest
+
python:
# For available versions, see:
# https://docs.readthedocs.io/en/stable/config-file/v2.html#build-image
- version: 3.7 # Keep in sync with .travis.yml
+ version: 3.8 # Keep in sync with .github/workflows/checks.yml
install:
- requirements: docs/requirements.txt
- path: .
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d6ec88e06..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-language: python
-dist: xenial
-branches:
- only:
- - master
- - /^\d\.\d+$/
- - /^\d\.\d+\.\d+(rc\d+|\.dev\d+)?$/
-matrix:
- include:
- - env: TOXENV=security
- python: 3.8
- - env: TOXENV=flake8
- python: 3.8
- - env: TOXENV=pylint
- python: 3.8
- - env: TOXENV=docs
- python: 3.7 # Keep in sync with .readthedocs.yml
-
- - env: TOXENV=pypy3
- - env: TOXENV=pinned
- python: 3.5.1
- dist: trusty
- - env: TOXENV=asyncio
- python: 3.5.1 # We use additional code to support 3.5.3 and earlier
- dist: trusty
- - env: TOXENV=py
- python: 3.5
- - env: TOXENV=asyncio
- python: 3.5 # We use specific code to support >= 3.5.4, < 3.6
- - env: TOXENV=py
- python: 3.6
- - env: TOXENV=py
- python: 3.7
- - env: TOXENV=py PYPI_RELEASE_JOB=true
- python: 3.8
- dist: bionic
- - env: TOXENV=extra-deps
- python: 3.8
- dist: bionic
- - env: TOXENV=asyncio
- python: 3.8
- dist: bionic
-install:
- - |
- if [ "$TOXENV" = "pypy3" ]; then
- export PYPY_VERSION="pypy3.5-5.9-beta-linux_x86_64-portable"
- wget "https://bitbucket.org/squeaky/portable-pypy/downloads/${PYPY_VERSION}.tar.bz2"
- tar -jxf ${PYPY_VERSION}.tar.bz2
- virtualenv --python="$PYPY_VERSION/bin/pypy3" "$HOME/virtualenvs/$PYPY_VERSION"
- source "$HOME/virtualenvs/$PYPY_VERSION/bin/activate"
- fi
- - pip install -U tox twine wheel codecov
-
-script: tox
-after_success:
- - codecov
-notifications:
- irc:
- use_notice: true
- skip_join: true
- channels:
- - irc.freenode.org#scrapy
-cache:
- directories:
- - $HOME/.cache/pip
-deploy:
- provider: pypi
- distributions: "sdist bdist_wheel"
- user: scrapy
- password:
- secure: JaAKcy1AXWXDK3LXdjOtKyaVPCSFoCGCnW15g4f65E/8Fsi9ZzDfmBa4Equs3IQb/vs/if2SVrzJSr7arN7r9Z38Iv1mUXHkFAyA3Ym8mThfABBzzcUWEQhIHrCX0Tdlx9wQkkhs+PZhorlmRS4gg5s6DzPaeA2g8SCgmlRmFfA=
- on:
- tags: true
- repo: scrapy/scrapy
- condition: "$PYPI_RELEASE_JOB == true && $TRAVIS_TAG =~ ^[0-9]+[.][0-9]+[.][0-9]+(rc[0-9]+|[.]dev[0-9]+)?$"
diff --git a/AUTHORS b/AUTHORS
index bcaa1ecd3..9706adf42 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,8 +1,8 @@
Scrapy was brought to life by Shane Evans while hacking a scraping framework
prototype for Mydeco (mydeco.com). It soon became maintained, extended and
improved by Insophia (insophia.com), with the initial sponsorship of Mydeco to
-bootstrap the project. In mid-2011, Scrapinghub became the new official
-maintainer.
+bootstrap the project. In mid-2011, Scrapinghub (now Zyte) became the new
+official maintainer.
Here is the list of the primary authors & contributors:
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index d1cd3e517..902cd523e 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at opensource@scrapinghub.com. All
+reported by contacting the project team at opensource@zyte.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -72,3 +72,6 @@ available at [http://contributor-covenant.org/version/1/4][version].
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/README.rst b/README.rst
index fd84e127e..5750e2c0f 100644
--- a/README.rst
+++ b/README.rst
@@ -10,9 +10,17 @@ Scrapy
:target: https://pypi.python.org/pypi/Scrapy
:alt: Supported Python Versions
-.. image:: https://img.shields.io/travis/scrapy/scrapy/master.svg
- :target: https://travis-ci.org/scrapy/scrapy
- :alt: Build Status
+.. image:: https://github.com/scrapy/scrapy/workflows/Ubuntu/badge.svg
+ :target: https://github.com/scrapy/scrapy/actions?query=workflow%3AUbuntu
+ :alt: Ubuntu
+
+.. image:: https://github.com/scrapy/scrapy/workflows/macOS/badge.svg
+ :target: https://github.com/scrapy/scrapy/actions?query=workflow%3AmacOS
+ :alt: macOS
+
+.. image:: https://github.com/scrapy/scrapy/workflows/Windows/badge.svg
+ :target: https://github.com/scrapy/scrapy/actions?query=workflow%3AWindows
+ :alt: Windows
.. image:: https://img.shields.io/badge/wheel-yes-brightgreen.svg
:target: https://pypi.python.org/pypi/Scrapy
@@ -34,13 +42,20 @@ Scrapy is a fast high-level web crawling and web scraping framework, used to
crawl websites and extract structured data from their pages. It can be used for
a wide range of purposes, from data mining to monitoring and automated testing.
+Scrapy is maintained by Zyte_ (formerly Scrapinghub) and `many other
+contributors`_.
+
+.. _many other contributors: https://github.com/scrapy/scrapy/graphs/contributors
+.. _Zyte: https://www.zyte.com/
+
Check the Scrapy homepage at https://scrapy.org for more information,
including a list of features.
+
Requirements
============
-* Python 3.5.1+
+* Python 3.6+
* Works on Linux, Windows, macOS, BSD
Install
@@ -81,7 +96,7 @@ Please note that this project is released with a Contributor Code of Conduct
(see https://github.com/scrapy/scrapy/blob/master/CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.
-Please report unacceptable behavior to opensource@scrapinghub.com.
+Please report unacceptable behavior to opensource@zyte.com.
Companies using Scrapy
======================
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 7fd636864..000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-platform: x86
-version: '{branch}-{build}'
-environment:
- matrix:
- - PYTHON: "C:\\Python36"
- TOX_ENV: py36
-
-branches:
- only:
- - master
- - /d+\.\d+\.\d+[\w\-]*$/
-
-install:
- - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- - "SET PYTHONPATH=%APPVEYOR_BUILD_FOLDER%"
- - "SET TOX_TESTENV_PASSENV=HOME HOMEDRIVE HOMEPATH PYTHONPATH USERPROFILE"
- - "pip install -U tox"
-
-build: false
-skip_tags: true
-test_script:
- - "tox -e %TOX_ENV%"
-
-cache:
- - '%LOCALAPPDATA%\pip\cache'
diff --git a/conftest.py b/conftest.py
index b39d644a5..05b4ccdad 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,6 +1,11 @@
from pathlib import Path
import pytest
+from twisted.web.http import H2_ENABLED
+
+from scrapy.utils.reactor import install_reactor
+
+from tests.keys import generate_keys
def _py_files(folder):
@@ -14,14 +19,21 @@ collect_ignore = [
*_py_files("tests/CrawlerProcess"),
# contains scripts to be run by tests/test_crawler.py::CrawlerRunnerSubprocess
*_py_files("tests/CrawlerRunner"),
- # Py36-only parts of respective tests
- *_py_files("tests/py36"),
]
-for line in open('tests/ignores.txt'):
- file_path = line.strip()
- if file_path and file_path[0] != '#':
- collect_ignore.append(file_path)
+with open('tests/ignores.txt') as reader:
+ for line in reader:
+ file_path = line.strip()
+ if file_path and file_path[0] != '#':
+ collect_ignore.append(file_path)
+
+if not H2_ENABLED:
+ collect_ignore.extend(
+ (
+ 'scrapy/core/downloader/handlers/http2.py',
+ *_py_files("scrapy/core/http2"),
+ )
+ )
@pytest.fixture()
@@ -40,6 +52,14 @@ def pytest_collection_modifyitems(session, config, items):
pass
+def pytest_addoption(parser):
+ parser.addoption(
+ "--reactor",
+ default="default",
+ choices=["default", "asyncio"],
+ )
+
+
@pytest.fixture(scope='class')
def reactor_pytest(request):
if not request.cls:
@@ -53,3 +73,12 @@ def reactor_pytest(request):
def only_asyncio(request, reactor_pytest):
if request.node.get_closest_marker('only_asyncio') and reactor_pytest != 'asyncio':
pytest.skip('This test is only run with --reactor=asyncio')
+
+
+def pytest_configure(config):
+ if config.getoption("--reactor") == "asyncio":
+ install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
+
+
+# Generate localhost certificate files, needed by some tests
+generate_keys()
diff --git a/docs/Makefile b/docs/Makefile
index ff68bf1ae..87d5d3047 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -8,7 +8,7 @@ PYTHON = python
SPHINXOPTS =
PAPER =
SOURCES =
-SHELL = /bin/bash
+SHELL = /usr/bin/env bash
ALLSPHINXOPTS = -b $(BUILDER) -d build/doctrees \
-D latex_elements.papersize=$(PAPER) \
diff --git a/docs/_ext/scrapydocs.py b/docs/_ext/scrapydocs.py
index 192123473..640660943 100644
--- a/docs/_ext/scrapydocs.py
+++ b/docs/_ext/scrapydocs.py
@@ -17,7 +17,7 @@ class SettingsListDirective(Directive):
def is_setting_index(node):
if node.tagname == 'index':
# index entries for setting directives look like:
- # [(u'pair', u'SETTING_NAME; setting', u'std:setting-SETTING_NAME', '')]
+ # [('pair', 'SETTING_NAME; setting', 'std:setting-SETTING_NAME', '')]
entry_type, info, refid = node['entries'][0][:3]
return entry_type == 'pair' and info.endswith('; setting')
return False
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 000000000..64f16939c
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,10 @@
+/* Move lists closer to their introducing paragraph */
+.rst-content .section ol p, .rst-content .section ul p {
+ margin-bottom: 0px;
+}
+.rst-content p + ol, .rst-content p + ul {
+ margin-top: -18px; /* Compensates margin-top: 24px of p */
+}
+.rst-content dl p + ol, .rst-content dl p + ul {
+ margin-top: -6px; /* Compensates margin-top: 12px of p */
+}
\ No newline at end of file
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
index a6f6cbda8..18a5231ee 100644
--- a/docs/_templates/layout.html
+++ b/docs/_templates/layout.html
@@ -3,14 +3,9 @@
{% block footer %}
{{ super() }}
{% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
index 29b2fc406..406c4d94a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -49,7 +49,7 @@ master_doc = 'index'
# General information about the project.
project = 'Scrapy'
-copyright = '2008–{}, Scrapy developers'.format(datetime.now().year)
+copyright = f'2008–{datetime.now().year}, Scrapy developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -122,7 +122,6 @@ html_theme = 'sphinx_rtd_theme'
import sphinx_rtd_theme
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
@@ -183,6 +182,10 @@ html_copy_source = True
# Output file base name for HTML help builder.
htmlhelp_basename = 'Scrapydoc'
+html_css_files = [
+ 'custom.css',
+]
+
# Options for LaTeX output
# ------------------------
@@ -281,8 +284,11 @@ coverage_ignore_pyobjects = [
# -------------------------------------
intersphinx_mapping = {
+ 'attrs': ('https://www.attrs.org/en/stable/', None),
'coverage': ('https://coverage.readthedocs.io/en/stable', None),
+ 'cryptography' : ('https://cryptography.io/en/latest/', None),
'cssselect': ('https://cssselect.readthedocs.io/en/latest', None),
+ 'itemloaders': ('https://itemloaders.readthedocs.io/en/latest/', None),
'pytest': ('https://docs.pytest.org/en/latest', None),
'python': ('https://docs.python.org/3', None),
'sphinx': ('https://www.sphinx-doc.org/en/master', None),
@@ -304,3 +310,15 @@ hoverxref_role_types = {
"ref": "tooltip",
}
hoverxref_roles = ['command', 'reqmeta', 'setting', 'signal']
+
+
+def setup(app):
+ app.connect('autodoc-skip-member', maybe_skip_member)
+
+
+def maybe_skip_member(app, what, name, obj, skip, options):
+ if not skip:
+ # autodocs was generating a text "alias of" for the following members
+ # https://github.com/sphinx-doc/sphinx/issues/4422
+ return name in {'default_item_class', 'default_selector_class'}
+ return skip
diff --git a/docs/contributing.rst b/docs/contributing.rst
index aed5ab92e..4d2580a6c 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -108,6 +108,11 @@ Well-written patches should:
tox -e docs-coverage
+* if you are removing deprecated code, first make sure that at least 1 year
+ (12 months) has passed since the release that introduced the deprecation.
+ See :ref:`deprecation-policy`.
+
+
.. _submitting-patches:
Submitting patches
@@ -135,7 +140,7 @@ original pull request author hasn't had time to address them.
In this case consider picking up this pull request: open
a new pull request with all commits from the original pull request, as well as
additional changes to address the raised issues. Doing so helps a lot; it is
-not considered rude as soon as the original author is acknowledged by keeping
+not considered rude as long as the original author is acknowledged by keeping
his/her commits.
You can pull an existing pull request to a local branch
@@ -155,6 +160,9 @@ Finally, try to keep aesthetic changes (:pep:`8` compliance, unused imports
removal, etc) in separate commits from functional changes. This will make pull
requests easier to review and more likely to get merged.
+
+.. _coding-style:
+
Coding style
============
@@ -163,7 +171,7 @@ Scrapy:
* Unless otherwise specified, follow :pep:`8`.
-* It's OK to use lines longer than 80 chars if it improves the code
+* It's OK to use lines longer than 79 chars if it improves the code
readability.
* Don't put your name in the code you contribute; git provides enough
@@ -191,6 +199,17 @@ In any case, if something is covered in a docstring, use the
documentation instead of duplicating the docstring in files within the
``docs/`` directory.
+Documentation updates that cover new or modified features must use Sphinx’s
+:rst:dir:`versionadded` and :rst:dir:`versionchanged` directives. Use
+``VERSION`` as version, we will replace it with the actual version right before
+the corresponding release. When we release a new major or minor version of
+Scrapy, we remove these directives if they are older than 3 years.
+
+Documentation about deprecated features must be removed as those features are
+deprecated, so that new readers do not run into it. New deprecations and
+deprecation removals are documented in the :ref:`release notes `.
+
+
Tests
=====
diff --git a/docs/faq.rst b/docs/faq.rst
index c06cb945b..16903daea 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -64,20 +64,6 @@ Here's an example spider using BeautifulSoup API, with ``lxml`` as the HTML pars
.. _BeautifulSoup's official documentation: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#specifying-the-parser-to-use
-.. _faq-python-versions:
-
-What Python versions does Scrapy support?
------------------------------------------
-
-Scrapy is supported under Python 3.5.1+
-under CPython (default Python implementation) and PyPy (starting with PyPy 5.9).
-Python 3 support was added in Scrapy 1.1.
-PyPy support was added in Scrapy 1.4, PyPy3 support was added in Scrapy 1.5.
-Python 2 support was dropped in Scrapy 2.0.
-
-.. note::
- For Python 3 support on Windows, it is recommended to use
- Anaconda/Miniconda as :ref:`outlined in the installation guide `.
Did Scrapy "steal" X from Django?
---------------------------------
@@ -159,6 +145,41 @@ How can I make Scrapy consume less memory?
See previous question.
+How can I prevent memory errors due to many allowed domains?
+------------------------------------------------------------
+
+If you have a spider with a long list of
+:attr:`~scrapy.Spider.allowed_domains` (e.g. 50,000+), consider
+replacing the default
+:class:`~scrapy.spidermiddlewares.offsite.OffsiteMiddleware` spider middleware
+with a :ref:`custom spider middleware ` that requires
+less memory. For example:
+
+- If your domain names are similar enough, use your own regular expression
+ instead joining the strings in
+ :attr:`~scrapy.Spider.allowed_domains` into a complex regular
+ expression.
+
+- If you can `meet the installation requirements`_, use pyre2_ instead of
+ Python’s re_ to compile your URL-filtering regular expression. See
+ :issue:`1908`.
+
+See also other suggestions at `StackOverflow`_.
+
+.. note:: Remember to disable
+ :class:`scrapy.spidermiddlewares.offsite.OffsiteMiddleware` when you enable
+ your custom implementation::
+
+ SPIDER_MIDDLEWARES = {
+ 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
+ 'myproject.middlewares.CustomOffsiteMiddleware': 500,
+ }
+
+.. _meet the installation requirements: https://github.com/andreasvc/pyre2#installation
+.. _pyre2: https://github.com/andreasvc/pyre2
+.. _re: https://docs.python.org/library/re.html
+.. _StackOverflow: https://stackoverflow.com/q/36440681/939364
+
Can I use Basic HTTP Authentication in my spiders?
--------------------------------------------------
@@ -250,15 +271,15 @@ Simplest way to dump all my scraped items into a JSON/CSV/XML file?
To dump into a JSON file::
- scrapy crawl myspider -o items.json
+ scrapy crawl myspider -O items.json
To dump into a CSV file::
- scrapy crawl myspider -o items.csv
+ scrapy crawl myspider -O items.csv
To dump into a XML file::
- scrapy crawl myspider -o items.xml
+ scrapy crawl myspider -O items.xml
For more information see :ref:`topics-feed-exports`
@@ -342,15 +363,15 @@ method for this purpose. For example::
from copy import deepcopy
- from scrapy.item import Item
-
+ from itemadapter import is_item, ItemAdapter
class MultiplyItemsMiddleware:
def process_spider_output(self, response, result, spider):
for item in result:
- if isinstance(item, (Item, dict)):
- for _ in range(item['multiply_by']):
+ if is_item(item):
+ adapter = ItemAdapter(item)
+ for _ in range(adapter['multiply_by']):
yield deepcopy(item)
Does Scrapy support IPv6 addresses?
@@ -377,11 +398,12 @@ How can I cancel the download of a given response?
--------------------------------------------------
In some situations, it might be useful to stop the download of a certain response.
-For instance, if you only need the first part of a large response and you would like
-to save resources by avoiding the download of the whole body.
-In that case, you could attach a handler to the :class:`~scrapy.signals.bytes_received`
-signal and raise a :exc:`~scrapy.exceptions.StopDownload` exception. Please refer to
-the :ref:`topics-stop-response-download` topic for additional information and examples.
+For instance, sometimes you can determine whether or not you need the full contents
+of a response by inspecting its headers or the first bytes of its body. In that case,
+you could save resources by attaching a handler to the :class:`~scrapy.signals.bytes_received`
+or :class:`~scrapy.signals.headers_received` signals and raising a
+:exc:`~scrapy.exceptions.StopDownload` exception. Please refer to the
+:ref:`topics-stop-response-download` topic for additional information and examples.
.. _has been reported: https://github.com/scrapy/scrapy/issues/2905
diff --git a/docs/index.rst b/docs/index.rst
index 11aa5c9be..433798aa8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -78,7 +78,6 @@ Basic concepts
topics/settings
topics/exceptions
-
:doc:`topics/commands`
Learn about the command-line tool used to manage your Scrapy project.
@@ -228,6 +227,7 @@ Extending Scrapy
topics/extensions
topics/api
topics/signals
+ topics/scheduler
topics/exporters
@@ -249,6 +249,9 @@ Extending Scrapy
:doc:`topics/signals`
See all available signals and how to work with them.
+:doc:`topics/scheduler`
+ Understand the scheduler component.
+
:doc:`topics/exporters`
Quickly export your scraped items to a file (XML, CSV, etc).
diff --git a/docs/intro/install.rst b/docs/intro/install.rst
index 4af80d801..8581dde0b 100644
--- a/docs/intro/install.rst
+++ b/docs/intro/install.rst
@@ -4,12 +4,19 @@
Installation guide
==================
+.. _faq-python-versions:
+
+Supported Python versions
+=========================
+
+Scrapy requires Python 3.6+, either the CPython implementation (default) or
+the PyPy 7.2.0+ implementation (see :ref:`python:implementations`).
+
+.. _intro-install-scrapy:
+
Installing Scrapy
=================
-Scrapy runs on Python 3.5.1 or above under CPython (default Python
-implementation) and PyPy (starting with PyPy 5.9).
-
If you're using `Anaconda`_ or `Miniconda`_, you can install the package from
the `conda-forge`_ channel, which has up-to-date packages for Linux, Windows
and macOS.
@@ -23,13 +30,13 @@ you can install Scrapy and its dependencies from PyPI with::
pip install Scrapy
+We strongly recommend that you install Scrapy in :ref:`a dedicated virtualenv `,
+to avoid conflicting with your system packages.
+
Note that sometimes this may require solving compilation issues for some Scrapy
dependencies depending on your operating system, so be sure to check the
:ref:`intro-install-platform-notes`.
-We strongly recommend that you install Scrapy in :ref:`a dedicated virtualenv `,
-to avoid conflicting with your system packages.
-
For more detailed and platform specifics instructions, as well as
troubleshooting information, read on.
@@ -63,10 +70,9 @@ In case of any trouble related to these dependencies,
please refer to their respective installation instructions:
* `lxml installation`_
-* `cryptography installation`_
+* :doc:`cryptography installation `
.. _lxml installation: https://lxml.de/installation.html
-.. _cryptography installation: https://cryptography.io/en/latest/installation/
.. _intro-using-virtualenv:
@@ -112,6 +118,27 @@ Once you've installed `Anaconda`_ or `Miniconda`_, install Scrapy with::
conda install -c conda-forge scrapy
+To install Scrapy on Windows using ``pip``:
+
+.. warning::
+ This installation method requires “Microsoft Visual C++” for installing some
+ Scrapy dependencies, which demands significantly more disk space than Anaconda.
+
+#. Download and execute `Microsoft C++ Build Tools`_ to install the Visual Studio Installer.
+
+#. Run the Visual Studio Installer.
+
+#. Under the Workloads section, select **C++ build tools**.
+
+#. Check the installation details and make sure following packages are selected as optional components:
+
+ * **MSVC** (e.g MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.23) )
+
+ * **Windows SDK** (e.g Windows 10 SDK (10.0.18362.0))
+
+#. Install the Visual Studio Build Tools.
+
+Now, you should be able to :ref:`install Scrapy ` using ``pip``.
.. _intro-install-ubuntu:
@@ -205,8 +232,8 @@ PyPy
We recommend using the latest PyPy version. The version tested is 5.9.0.
For PyPy3, only Linux installation was tested.
-Most Scrapy dependencides now have binary wheels for CPython, but not for PyPy.
-This means that these dependecies will be built during installation.
+Most Scrapy dependencies now have binary wheels for CPython, but not for PyPy.
+This means that these dependencies will be built during installation.
On macOS, you are likely to face an issue with building Cryptography dependency,
solution to this problem is described
`here `_,
@@ -259,10 +286,10 @@ For details, see `Issue #2473 `_.
.. _cryptography: https://cryptography.io/en/latest/
.. _pyOpenSSL: https://pypi.org/project/pyOpenSSL/
.. _setuptools: https://pypi.python.org/pypi/setuptools
-.. _AUR Scrapy package: https://aur.archlinux.org/packages/scrapy/
.. _homebrew: https://brew.sh/
.. _zsh: https://www.zsh.org/
-.. _Scrapinghub: https://scrapinghub.com
.. _Anaconda: https://docs.anaconda.com/anaconda/
.. _Miniconda: https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html
+.. _Visual Studio: https://docs.microsoft.com/en-us/visualstudio/install/install-visual-studio
+.. _Microsoft C++ Build Tools: https://visualstudio.microsoft.com/visual-cpp-build-tools/
.. _conda-forge: https://conda-forge.org/
diff --git a/docs/intro/overview.rst b/docs/intro/overview.rst
index 01986b594..dd80c7bd0 100644
--- a/docs/intro/overview.rst
+++ b/docs/intro/overview.rst
@@ -42,30 +42,18 @@ http://quotes.toscrape.com, following the pagination::
if next_page is not None:
yield response.follow(next_page, self.parse)
-
Put this in a text file, name it to something like ``quotes_spider.py``
and run the spider using the :command:`runspider` command::
- scrapy runspider quotes_spider.py -o quotes.json
+ scrapy runspider quotes_spider.py -o quotes.jl
+When this finishes you will have in the ``quotes.jl`` file a list of the
+quotes in JSON Lines format, containing text and author, looking like this::
-When this finishes you will have in the ``quotes.json`` file a list of the
-quotes in JSON format, containing text and author, looking like this (reformatted
-here for better readability)::
-
- [{
- "author": "Jane Austen",
- "text": "\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d"
- },
- {
- "author": "Groucho Marx",
- "text": "\u201cOutside of a dog, a book is man's best friend. Inside of a dog it's too dark to read.\u201d"
- },
- {
- "author": "Steve Martin",
- "text": "\u201cA day without sunshine is like, you know, night.\u201d"
- },
- ...]
+ {"author": "Jane Austen", "text": "\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d"}
+ {"author": "Steve Martin", "text": "\u201cA day without sunshine is like, you know, night.\u201d"}
+ {"author": "Garrison Keillor", "text": "\u201cAnyone who thinks sitting in church can make you a Christian must also think that sitting in a garage can make you a car.\u201d"}
+ ...
What just happened?
diff --git a/docs/intro/tutorial.rst b/docs/intro/tutorial.rst
index 5f35dc936..438f3d6df 100644
--- a/docs/intro/tutorial.rst
+++ b/docs/intro/tutorial.rst
@@ -78,7 +78,7 @@ Our first Spider
Spiders are classes that you define and that Scrapy uses to scrape information
from a website (or a group of websites). They must subclass
-:class:`~scrapy.spiders.Spider` and define the initial requests to make,
+:class:`~scrapy.Spider` and define the initial requests to make,
optionally how to follow links in the pages, and how to parse the downloaded
page content to extract data.
@@ -101,32 +101,32 @@ This is the code for our first Spider. Save it in a file named
def parse(self, response):
page = response.url.split("/")[-2]
- filename = 'quotes-%s.html' % page
+ filename = f'quotes-{page}.html'
with open(filename, 'wb') as f:
f.write(response.body)
- self.log('Saved file %s' % filename)
+ self.log(f'Saved file {filename}')
-As you can see, our Spider subclasses :class:`scrapy.Spider `
+As you can see, our Spider subclasses :class:`scrapy.Spider `
and defines some attributes and methods:
-* :attr:`~scrapy.spiders.Spider.name`: identifies the Spider. It must be
+* :attr:`~scrapy.Spider.name`: identifies the Spider. It must be
unique within a project, that is, you can't set the same name for different
Spiders.
-* :meth:`~scrapy.spiders.Spider.start_requests`: must return an iterable of
+* :meth:`~scrapy.Spider.start_requests`: must return an iterable of
Requests (you can return a list of requests or write a generator function)
which the Spider will begin to crawl from. Subsequent requests will be
generated successively from these initial requests.
-* :meth:`~scrapy.spiders.Spider.parse`: a method that will be called to handle
+* :meth:`~scrapy.Spider.parse`: a method that will be called to handle
the response downloaded for each of the requests made. The response parameter
is an instance of :class:`~scrapy.http.TextResponse` that holds
the page content and has further helpful methods to handle it.
- The :meth:`~scrapy.spiders.Spider.parse` method usually parses the response, extracting
+ The :meth:`~scrapy.Spider.parse` method usually parses the response, extracting
the scraped data as dicts and also finding new URLs to
- follow and creating new requests (:class:`~scrapy.http.Request`) from them.
+ follow and creating new requests (:class:`~scrapy.Request`) from them.
How to run our spider
---------------------
@@ -162,7 +162,7 @@ for the respective URLs, as our ``parse`` method instructs.
What just happened under the hood?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Scrapy schedules the :class:`scrapy.Request ` objects
+Scrapy schedules the :class:`scrapy.Request ` objects
returned by the ``start_requests`` method of the Spider. Upon receiving a
response for each one, it instantiates :class:`~scrapy.http.Response` objects
and calls the callback method associated with the request (in this case, the
@@ -171,11 +171,11 @@ and calls the callback method associated with the request (in this case, the
A shortcut to the start_requests method
---------------------------------------
-Instead of implementing a :meth:`~scrapy.spiders.Spider.start_requests` method
-that generates :class:`scrapy.Request ` objects from URLs,
-you can just define a :attr:`~scrapy.spiders.Spider.start_urls` class attribute
+Instead of implementing a :meth:`~scrapy.Spider.start_requests` method
+that generates :class:`scrapy.Request ` objects from URLs,
+you can just define a :attr:`~scrapy.Spider.start_urls` class attribute
with a list of URLs. This list will then be used by the default implementation
-of :meth:`~scrapy.spiders.Spider.start_requests` to create the initial requests
+of :meth:`~scrapy.Spider.start_requests` to create the initial requests
for your spider::
import scrapy
@@ -190,13 +190,13 @@ for your spider::
def parse(self, response):
page = response.url.split("/")[-2]
- filename = 'quotes-%s.html' % page
+ filename = f'quotes-{page}.html'
with open(filename, 'wb') as f:
f.write(response.body)
-The :meth:`~scrapy.spiders.Spider.parse` method will be called to handle each
+The :meth:`~scrapy.Spider.parse` method will be called to handle each
of the requests for those URLs, even though we haven't explicitly told Scrapy
-to do so. This happens because :meth:`~scrapy.spiders.Spider.parse` is Scrapy's
+to do so. This happens because :meth:`~scrapy.Spider.parse` is Scrapy's
default callback method, which is called for requests without an explicitly
assigned callback.
@@ -248,7 +248,7 @@ object:
The result of running ``response.css('title')`` is a list-like object called
:class:`~scrapy.selector.SelectorList`, which represents a list of
-:class:`~scrapy.selector.Selector` objects that wrap around XML/HTML elements
+:class:`~scrapy.Selector` objects that wrap around XML/HTML elements
and allow you to run further queries to fine-grain the selection or extract the
data.
@@ -405,8 +405,6 @@ to get all of them:
from sys import version_info
-.. skip: next if(version_info < (3, 6), reason="Only Python 3.6+ dictionaries match the output")
-
Having figured out how to extract each bit, we can now iterate over all the
quotes elements and put them together into a Python dictionary:
@@ -464,16 +462,15 @@ Storing the scraped data
The simplest way to store the scraped data is by using :ref:`Feed exports
`, with the following command::
- scrapy crawl quotes -o quotes.json
+ scrapy crawl quotes -O quotes.json
-That will generate an ``quotes.json`` file containing all scraped items,
+That will generate a ``quotes.json`` file containing all scraped items,
serialized in `JSON`_.
-For historic reasons, Scrapy appends to a given file instead of overwriting
-its contents. If you run this command twice without removing the file
-before the second time, you'll end up with a broken JSON file.
-
-You can also use other formats, like `JSON Lines`_::
+The ``-O`` command-line switch overwrites any existing file; use ``-o`` instead
+to append new content to any existing file. However, appending to a JSON file
+makes the file contents invalid JSON. When appending to a file, consider
+using a different serialization format, such as `JSON Lines`_::
scrapy crawl quotes -o quotes.jl
@@ -673,7 +670,7 @@ the pagination links with the ``parse`` callback as we saw before.
Here we're passing callbacks to
:meth:`response.follow_all ` as positional
arguments to make the code shorter; it also works for
-:class:`~scrapy.http.Request`.
+:class:`~scrapy.Request`.
The ``parse_author`` callback defines a helper function to extract and cleanup the
data from a CSS query and yields the Python dict with the author data.
@@ -704,7 +701,7 @@ Using spider arguments
You can provide command line arguments to your spiders by using the ``-a``
option when running them::
- scrapy crawl quotes -o quotes-humor.json -a tag=humor
+ scrapy crawl quotes -O quotes-humor.json -a tag=humor
These arguments are passed to the Spider's ``__init__`` method and become
spider attributes by default.
diff --git a/docs/news.rst b/docs/news.rst
index a158246eb..0ea412e75 100644
--- a/docs/news.rst
+++ b/docs/news.rst
@@ -3,6 +3,847 @@
Release notes
=============
+.. _release-2.5.0:
+
+Scrapy 2.5.0 (2021-04-06)
+-------------------------
+
+Highlights:
+
+- Official Python 3.9 support
+
+- Experimental :ref:`HTTP/2 support `
+
+- New :func:`~scrapy.downloadermiddlewares.retry.get_retry_request` function
+ to retry requests from spider callbacks
+
+- New :class:`~scrapy.signals.headers_received` signal that allows stopping
+ downloads early
+
+- New :class:`Response.protocol ` attribute
+
+Deprecation removals
+~~~~~~~~~~~~~~~~~~~~
+
+- Removed all code that :ref:`was deprecated in 1.7.0 <1.7-deprecations>` and
+ had not :ref:`already been removed in 2.4.0 <2.4-deprecation-removals>`.
+ (:issue:`4901`)
+
+- Removed support for the ``SCRAPY_PICKLED_SETTINGS_TO_OVERRIDE`` environment
+ variable, :ref:`deprecated in 1.8.0 <1.8-deprecations>`. (:issue:`4912`)
+
+
+Deprecations
+~~~~~~~~~~~~
+
+- The :mod:`scrapy.utils.py36` module is now deprecated in favor of
+ :mod:`scrapy.utils.asyncgen`. (:issue:`4900`)
+
+
+New features
+~~~~~~~~~~~~
+
+- Experimental :ref:`HTTP/2 support ` through a new download handler
+ that can be assigned to the ``https`` protocol in the
+ :setting:`DOWNLOAD_HANDLERS` setting.
+ (:issue:`1854`, :issue:`4769`, :issue:`5058`, :issue:`5059`, :issue:`5066`)
+
+- The new :func:`scrapy.downloadermiddlewares.retry.get_retry_request`
+ function may be used from spider callbacks or middlewares to handle the
+ retrying of a request beyond the scenarios that
+ :class:`~scrapy.downloadermiddlewares.retry.RetryMiddleware` supports.
+ (:issue:`3590`, :issue:`3685`, :issue:`4902`)
+
+- The new :class:`~scrapy.signals.headers_received` signal gives early access
+ to response headers and allows :ref:`stopping downloads
+ `.
+ (:issue:`1772`, :issue:`4897`)
+
+- The new :attr:`Response.protocol `
+ attribute gives access to the string that identifies the protocol used to
+ download a response. (:issue:`4878`)
+
+- :ref:`Stats ` now include the following entries that indicate
+ the number of successes and failures in storing
+ :ref:`feeds `::
+
+ feedexport/success_count/
+ feedexport/failed_count/
+
+ Where ```` is the feed storage backend class name, such as
+ :class:`~scrapy.extensions.feedexport.FileFeedStorage` or
+ :class:`~scrapy.extensions.feedexport.FTPFeedStorage`.
+
+ (:issue:`3947`, :issue:`4850`)
+
+- The :class:`~scrapy.spidermiddlewares.urllength.UrlLengthMiddleware` spider
+ middleware now logs ignored URLs with ``INFO`` :ref:`logging level
+ ` instead of ``DEBUG``, and it now includes the following entry
+ into :ref:`stats ` to keep track of the number of ignored
+ URLs::
+
+ urllength/request_ignored_count
+
+ (:issue:`5036`)
+
+- The
+ :class:`~scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware`
+ downloader middleware now logs the number of decompressed responses and the
+ total count of resulting bytes::
+
+ httpcompression/response_bytes
+ httpcompression/response_count
+
+ (:issue:`4797`, :issue:`4799`)
+
+
+Bug fixes
+~~~~~~~~~
+
+- Fixed installation on PyPy installing PyDispatcher in addition to
+ PyPyDispatcher, which could prevent Scrapy from working depending on which
+ package got imported. (:issue:`4710`, :issue:`4814`)
+
+- When inspecting a callback to check if it is a generator that also returns
+ a value, an exception is no longer raised if the callback has a docstring
+ with lower indentation than the following code.
+ (:issue:`4477`, :issue:`4935`)
+
+- The `Content-Length `_
+ header is no longer omitted from responses when using the default, HTTP/1.1
+ download handler (see :setting:`DOWNLOAD_HANDLERS`).
+ (:issue:`5009`, :issue:`5034`, :issue:`5045`, :issue:`5057`, :issue:`5062`)
+
+- Setting the :reqmeta:`handle_httpstatus_all` request meta key to ``False``
+ now has the same effect as not setting it at all, instead of having the
+ same effect as setting it to ``True``.
+ (:issue:`3851`, :issue:`4694`)
+
+
+Documentation
+~~~~~~~~~~~~~
+
+- Added instructions to :ref:`install Scrapy in Windows using pip
+ `.
+ (:issue:`4715`, :issue:`4736`)
+
+- Logging documentation now includes :ref:`additional ways to filter logs
+ `.
+ (:issue:`4216`, :issue:`4257`, :issue:`4965`)
+
+- Covered how to deal with long lists of allowed domains in the :ref:`FAQ
+ `. (:issue:`2263`, :issue:`3667`)
+
+- Covered scrapy-bench_ in :ref:`benchmarking`.
+ (:issue:`4996`, :issue:`5016`)
+
+- Clarified that one :ref:`extension ` instance is created
+ per crawler.
+ (:issue:`5014`)
+
+- Fixed some errors in examples.
+ (:issue:`4829`, :issue:`4830`, :issue:`4907`, :issue:`4909`,
+ :issue:`5008`)
+
+- Fixed some external links, typos, and so on.
+ (:issue:`4892`, :issue:`4899`, :issue:`4936`, :issue:`4942`, :issue:`5005`,
+ :issue:`5063`)
+
+- The :ref:`list of Request.meta keys ` is now sorted
+ alphabetically.
+ (:issue:`5061`, :issue:`5065`)
+
+- Updated references to Scrapinghub, which is now called Zyte.
+ (:issue:`4973`, :issue:`5072`)
+
+- Added a mention to contributors in the README. (:issue:`4956`)
+
+- Reduced the top margin of lists. (:issue:`4974`)
+
+
+Quality Assurance
+~~~~~~~~~~~~~~~~~
+
+- Made Python 3.9 support official (:issue:`4757`, :issue:`4759`)
+
+- Extended typing hints (:issue:`4895`)
+
+- Fixed deprecated uses of the Twisted API.
+ (:issue:`4940`, :issue:`4950`, :issue:`5073`)
+
+- Made our tests run with the new pip resolver.
+ (:issue:`4710`, :issue:`4814`)
+
+- Added tests to ensure that :ref:`coroutine support `
+ is tested. (:issue:`4987`)
+
+- Migrated from Travis CI to GitHub Actions. (:issue:`4924`)
+
+- Fixed CI issues.
+ (:issue:`4986`, :issue:`5020`, :issue:`5022`, :issue:`5027`, :issue:`5052`,
+ :issue:`5053`)
+
+- Implemented code refactorings, style fixes and cleanups.
+ (:issue:`4911`, :issue:`4982`, :issue:`5001`, :issue:`5002`, :issue:`5076`)
+
+
+.. _release-2.4.1:
+
+Scrapy 2.4.1 (2020-11-17)
+-------------------------
+
+- Fixed :ref:`feed exports ` overwrite support (:issue:`4845`, :issue:`4857`, :issue:`4859`)
+
+- Fixed the AsyncIO event loop handling, which could make code hang
+ (:issue:`4855`, :issue:`4872`)
+
+- Fixed the IPv6-capable DNS resolver
+ :class:`~scrapy.resolver.CachingHostnameResolver` for download handlers
+ that call
+ :meth:`reactor.resolve `
+ (:issue:`4802`, :issue:`4803`)
+
+- Fixed the output of the :command:`genspider` command showing placeholders
+ instead of the import path of the generated spider module (:issue:`4874`)
+
+- Migrated Windows CI from Azure Pipelines to GitHub Actions (:issue:`4869`,
+ :issue:`4876`)
+
+
+.. _release-2.4.0:
+
+Scrapy 2.4.0 (2020-10-11)
+-------------------------
+
+Highlights:
+
+* Python 3.5 support has been dropped.
+
+* The ``file_path`` method of :ref:`media pipelines `
+ can now access the source :ref:`item `.
+
+ This allows you to set a download file path based on item data.
+
+* The new ``item_export_kwargs`` key of the :setting:`FEEDS` setting allows
+ to define keyword parameters to pass to :ref:`item exporter classes
+ `
+
+* You can now choose whether :ref:`feed exports `
+ overwrite or append to the output file.
+
+ For example, when using the :command:`crawl` or :command:`runspider`
+ commands, you can use the ``-O`` option instead of ``-o`` to overwrite the
+ output file.
+
+* Zstd-compressed responses are now supported if zstandard_ is installed.
+
+* In settings, where the import path of a class is required, it is now
+ possible to pass a class object instead.
+
+Modified requirements
+~~~~~~~~~~~~~~~~~~~~~
+
+* Python 3.6 or greater is now required; support for Python 3.5 has been
+ dropped
+
+ As a result:
+
+ - When using PyPy, PyPy 7.2.0 or greater :ref:`is now required
+ `
+
+ - For Amazon S3 storage support in :ref:`feed exports
+ ` or :ref:`media pipelines
+ `, botocore_ 1.4.87 or greater is now required
+
+ - To use the :ref:`images pipeline `, Pillow_ 4.0.0 or
+ greater is now required
+
+ (:issue:`4718`, :issue:`4732`, :issue:`4733`, :issue:`4742`, :issue:`4743`,
+ :issue:`4764`)
+
+
+Backward-incompatible changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* :class:`~scrapy.downloadermiddlewares.cookies.CookiesMiddleware` once again
+ discards cookies defined in :attr:`Request.headers
+ `.
+
+ We decided to revert this bug fix, introduced in Scrapy 2.2.0, because it
+ was reported that the current implementation could break existing code.
+
+ If you need to set cookies for a request, use the :class:`Request.cookies
+ ` parameter.
+
+ A future version of Scrapy will include a new, better implementation of the
+ reverted bug fix.
+
+ (:issue:`4717`, :issue:`4823`)
+
+
+.. _2.4-deprecation-removals:
+
+Deprecation removals
+~~~~~~~~~~~~~~~~~~~~
+
+* :class:`scrapy.extensions.feedexport.S3FeedStorage` no longer reads the
+ values of ``access_key`` and ``secret_key`` from the running project
+ settings when they are not passed to its ``__init__`` method; you must
+ either pass those parameters to its ``__init__`` method or use
+ :class:`S3FeedStorage.from_crawler
+ `
+ (:issue:`4356`, :issue:`4411`, :issue:`4688`)
+
+* :attr:`Rule.process_request `
+ no longer admits callables which expect a single ``request`` parameter,
+ rather than both ``request`` and ``response`` (:issue:`4818`)
+
+
+Deprecations
+~~~~~~~~~~~~
+
+* In custom :ref:`media pipelines `, signatures that
+ do not accept a keyword-only ``item`` parameter in any of the methods that
+ :ref:`now support this parameter ` are now
+ deprecated (:issue:`4628`, :issue:`4686`)
+
+* In custom :ref:`feed storage backend classes `,
+ ``__init__`` method signatures that do not accept a keyword-only
+ ``feed_options`` parameter are now deprecated (:issue:`547`, :issue:`716`,
+ :issue:`4512`)
+
+* The :class:`scrapy.utils.python.WeakKeyCache` class is now deprecated
+ (:issue:`4684`, :issue:`4701`)
+
+* The :func:`scrapy.utils.boto.is_botocore` function is now deprecated, use
+ :func:`scrapy.utils.boto.is_botocore_available` instead (:issue:`4734`,
+ :issue:`4776`)
+
+
+New features
+~~~~~~~~~~~~
+
+.. _media-pipeline-item-parameter:
+
+* The following methods of :ref:`media pipelines ` now
+ accept an ``item`` keyword-only parameter containing the source
+ :ref:`item `:
+
+ - In :class:`scrapy.pipelines.files.FilesPipeline`:
+
+ - :meth:`~scrapy.pipelines.files.FilesPipeline.file_downloaded`
+
+ - :meth:`~scrapy.pipelines.files.FilesPipeline.file_path`
+
+ - :meth:`~scrapy.pipelines.files.FilesPipeline.media_downloaded`
+
+ - :meth:`~scrapy.pipelines.files.FilesPipeline.media_to_download`
+
+ - In :class:`scrapy.pipelines.images.ImagesPipeline`:
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.file_downloaded`
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.file_path`
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.get_images`
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.image_downloaded`
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.media_downloaded`
+
+ - :meth:`~scrapy.pipelines.images.ImagesPipeline.media_to_download`
+
+ (:issue:`4628`, :issue:`4686`)
+
+* The new ``item_export_kwargs`` key of the :setting:`FEEDS` setting allows
+ to define keyword parameters to pass to :ref:`item exporter classes
+ ` (:issue:`4606`, :issue:`4768`)
+
+* :ref:`Feed exports ` gained overwrite support:
+
+ * When using the :command:`crawl` or :command:`runspider` commands, you
+ can use the ``-O`` option instead of ``-o`` to overwrite the output
+ file
+
+ * You can use the ``overwrite`` key in the :setting:`FEEDS` setting to
+ configure whether to overwrite the output file (``True``) or append to
+ its content (``False``)
+
+ * The ``__init__`` and ``from_crawler`` methods of :ref:`feed storage
+ backend classes ` now receive a new keyword-only
+ parameter, ``feed_options``, which is a dictionary of :ref:`feed
+ options `
+
+ (:issue:`547`, :issue:`716`, :issue:`4512`)
+
+* Zstd-compressed responses are now supported if zstandard_ is installed
+ (:issue:`4831`)
+
+* In settings, where the import path of a class is required, it is now
+ possible to pass a class object instead (:issue:`3870`, :issue:`3873`).
+
+ This includes also settings where only part of its value is made of an
+ import path, such as :setting:`DOWNLOADER_MIDDLEWARES` or
+ :setting:`DOWNLOAD_HANDLERS`.
+
+* :ref:`Downloader middlewares ` can now
+ override :class:`response.request `.
+
+ If a :ref:`downloader middleware ` returns
+ a :class:`~scrapy.http.Response` object from
+ :meth:`~scrapy.downloadermiddlewares.DownloaderMiddleware.process_response`
+ or
+ :meth:`~scrapy.downloadermiddlewares.DownloaderMiddleware.process_exception`
+ with a custom :class:`~scrapy.http.Request` object assigned to
+ :class:`response.request `:
+
+ - The response is handled by the callback of that custom
+ :class:`~scrapy.http.Request` object, instead of being handled by the
+ callback of the original :class:`~scrapy.http.Request` object
+
+ - That custom :class:`~scrapy.http.Request` object is now sent as the
+ ``request`` argument to the :signal:`response_received` signal, instead
+ of the original :class:`~scrapy.http.Request` object
+
+ (:issue:`4529`, :issue:`4632`)
+
+* When using the :ref:`FTP feed storage backend `:
+
+ - It is now possible to set the new ``overwrite`` :ref:`feed option
+ ` to ``False`` to append to an existing file instead of
+ overwriting it
+
+ - The FTP password can now be omitted if it is not necessary
+
+ (:issue:`547`, :issue:`716`, :issue:`4512`)
+
+* The ``__init__`` method of :class:`~scrapy.exporters.CsvItemExporter` now
+ supports an ``errors`` parameter to indicate how to handle encoding errors
+ (:issue:`4755`)
+
+* When :ref:`using asyncio `, it is now possible to
+ :ref:`set a custom asyncio loop ` (:issue:`4306`,
+ :issue:`4414`)
+
+* Serialized requests (see :ref:`topics-jobs`) now support callbacks that are
+ spider methods that delegate on other callable (:issue:`4756`)
+
+* When a response is larger than :setting:`DOWNLOAD_MAXSIZE`, the logged
+ message is now a warning, instead of an error (:issue:`3874`,
+ :issue:`3886`, :issue:`4752`)
+
+
+Bug fixes
+~~~~~~~~~
+
+* The :command:`genspider` command no longer overwrites existing files
+ unless the ``--force`` option is used (:issue:`4561`, :issue:`4616`,
+ :issue:`4623`)
+
+* Cookies with an empty value are no longer considered invalid cookies
+ (:issue:`4772`)
+
+* The :command:`runspider` command now supports files with the ``.pyw`` file
+ extension (:issue:`4643`, :issue:`4646`)
+
+* The :class:`~scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware`
+ middleware now simply ignores unsupported proxy values (:issue:`3331`,
+ :issue:`4778`)
+
+* Checks for generator callbacks with a ``return`` statement no longer warn
+ about ``return`` statements in nested functions (:issue:`4720`,
+ :issue:`4721`)
+
+* The system file mode creation mask no longer affects the permissions of
+ files generated using the :command:`startproject` command (:issue:`4722`)
+
+* :func:`scrapy.utils.iterators.xmliter` now supports namespaced node names
+ (:issue:`861`, :issue:`4746`)
+
+* :class:`~scrapy.Request` objects can now have ``about:`` URLs, which can
+ work when using a headless browser (:issue:`4835`)
+
+
+Documentation
+~~~~~~~~~~~~~
+
+* The :setting:`FEED_URI_PARAMS` setting is now documented (:issue:`4671`,
+ :issue:`4724`)
+
+* Improved the documentation of
+ :ref:`link extractors ` with an usage example from
+ a spider callback and reference documentation for the
+ :class:`~scrapy.link.Link` class (:issue:`4751`, :issue:`4775`)
+
+* Clarified the impact of :setting:`CONCURRENT_REQUESTS` when using the
+ :class:`~scrapy.extensions.closespider.CloseSpider` extension
+ (:issue:`4836`)
+
+* Removed references to Python 2’s ``unicode`` type (:issue:`4547`,
+ :issue:`4703`)
+
+* We now have an :ref:`official deprecation policy `
+ (:issue:`4705`)
+
+* Our :ref:`documentation policies ` now cover usage
+ of Sphinx’s :rst:dir:`versionadded` and :rst:dir:`versionchanged`
+ directives, and we have removed usages referencing Scrapy 1.4.0 and earlier
+ versions (:issue:`3971`, :issue:`4310`)
+
+* Other documentation cleanups (:issue:`4090`, :issue:`4782`, :issue:`4800`,
+ :issue:`4801`, :issue:`4809`, :issue:`4816`, :issue:`4825`)
+
+
+Quality assurance
+~~~~~~~~~~~~~~~~~
+
+* Extended typing hints (:issue:`4243`, :issue:`4691`)
+
+* Added tests for the :command:`check` command (:issue:`4663`)
+
+* Fixed test failures on Debian (:issue:`4726`, :issue:`4727`, :issue:`4735`)
+
+* Improved Windows test coverage (:issue:`4723`)
+
+* Switched to :ref:`formatted string literals ` where possible
+ (:issue:`4307`, :issue:`4324`, :issue:`4672`)
+
+* Modernized :func:`super` usage (:issue:`4707`)
+
+* Other code and test cleanups (:issue:`1790`, :issue:`3288`, :issue:`4165`,
+ :issue:`4564`, :issue:`4651`, :issue:`4714`, :issue:`4738`, :issue:`4745`,
+ :issue:`4747`, :issue:`4761`, :issue:`4765`, :issue:`4804`, :issue:`4817`,
+ :issue:`4820`, :issue:`4822`, :issue:`4839`)
+
+
+.. _release-2.3.0:
+
+Scrapy 2.3.0 (2020-08-04)
+-------------------------
+
+Highlights:
+
+* :ref:`Feed exports ` now support :ref:`Google Cloud
+ Storage ` as a storage backend
+
+* The new :setting:`FEED_EXPORT_BATCH_ITEM_COUNT` setting allows to deliver
+ output items in batches of up to the specified number of items.
+
+ It also serves as a workaround for :ref:`delayed file delivery
+ `, which causes Scrapy to only start item delivery
+ after the crawl has finished when using certain storage backends
+ (:ref:`S3 `, :ref:`FTP `,
+ and now :ref:`GCS `).
+
+* The base implementation of :ref:`item loaders ` has been
+ moved into a separate library, :doc:`itemloaders `,
+ allowing usage from outside Scrapy and a separate release schedule
+
+Deprecation removals
+~~~~~~~~~~~~~~~~~~~~
+
+* Removed the following classes and their parent modules from
+ ``scrapy.linkextractors``:
+
+ * ``htmlparser.HtmlParserLinkExtractor``
+ * ``regex.RegexLinkExtractor``
+ * ``sgml.BaseSgmlLinkExtractor``
+ * ``sgml.SgmlLinkExtractor``
+
+ Use
+ :class:`LinkExtractor `
+ instead (:issue:`4356`, :issue:`4679`)
+
+
+Deprecations
+~~~~~~~~~~~~
+
+* The ``scrapy.utils.python.retry_on_eintr`` function is now deprecated
+ (:issue:`4683`)
+
+
+New features
+~~~~~~~~~~~~
+
+* :ref:`Feed exports ` support :ref:`Google Cloud
+ Storage ` (:issue:`685`, :issue:`3608`)
+
+* New :setting:`FEED_EXPORT_BATCH_ITEM_COUNT` setting for batch deliveries
+ (:issue:`4250`, :issue:`4434`)
+
+* The :command:`parse` command now allows specifying an output file
+ (:issue:`4317`, :issue:`4377`)
+
+* :meth:`Request.from_curl ` and
+ :func:`~scrapy.utils.curl.curl_to_request_kwargs` now also support
+ ``--data-raw`` (:issue:`4612`)
+
+* A ``parse`` callback may now be used in built-in spider subclasses, such
+ as :class:`~scrapy.spiders.CrawlSpider` (:issue:`712`, :issue:`732`,
+ :issue:`781`, :issue:`4254` )
+
+
+Bug fixes
+~~~~~~~~~
+
+* Fixed the :ref:`CSV exporting ` of
+ :ref:`dataclass items ` and :ref:`attr.s items
+ ` (:issue:`4667`, :issue:`4668`)
+
+* :meth:`Request.from_curl ` and
+ :func:`~scrapy.utils.curl.curl_to_request_kwargs` now set the request
+ method to ``POST`` when a request body is specified and no request method
+ is specified (:issue:`4612`)
+
+* The processing of ANSI escape sequences in enabled in Windows 10.0.14393
+ and later, where it is required for colored output (:issue:`4393`,
+ :issue:`4403`)
+
+
+Documentation
+~~~~~~~~~~~~~
+
+* Updated the `OpenSSL cipher list format`_ link in the documentation about
+ the :setting:`DOWNLOADER_CLIENT_TLS_CIPHERS` setting (:issue:`4653`)
+
+* Simplified the code example in :ref:`topics-loaders-dataclass`
+ (:issue:`4652`)
+
+.. _OpenSSL cipher list format: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT
+
+
+Quality assurance
+~~~~~~~~~~~~~~~~~
+
+* The base implementation of :ref:`item loaders ` has been
+ moved into :doc:`itemloaders ` (:issue:`4005`,
+ :issue:`4516`)
+
+* Fixed a silenced error in some scheduler tests (:issue:`4644`,
+ :issue:`4645`)
+
+* Renewed the localhost certificate used for SSL tests (:issue:`4650`)
+
+* Removed cookie-handling code specific to Python 2 (:issue:`4682`)
+
+* Stopped using Python 2 unicode literal syntax (:issue:`4704`)
+
+* Stopped using a backlash for line continuation (:issue:`4673`)
+
+* Removed unneeded entries from the MyPy exception list (:issue:`4690`)
+
+* Automated tests now pass on Windows as part of our continuous integration
+ system (:issue:`4458`)
+
+* Automated tests now pass on the latest PyPy version for supported Python
+ versions in our continuous integration system (:issue:`4504`)
+
+
+.. _release-2.2.1:
+
+Scrapy 2.2.1 (2020-07-17)
+-------------------------
+
+* The :command:`startproject` command no longer makes unintended changes to
+ the permissions of files in the destination folder, such as removing
+ execution permissions (:issue:`4662`, :issue:`4666`)
+
+
+.. _release-2.2.0:
+
+Scrapy 2.2.0 (2020-06-24)
+-------------------------
+
+Highlights:
+
+* Python 3.5.2+ is required now
+* :ref:`dataclass objects ` and
+ :ref:`attrs objects ` are now valid :ref:`item types
+ `
+* New :meth:`TextResponse.json ` method
+* New :signal:`bytes_received` signal that allows canceling response download
+* :class:`~scrapy.downloadermiddlewares.cookies.CookiesMiddleware` fixes
+
+Backward-incompatible changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* Support for Python 3.5.0 and 3.5.1 has been dropped; Scrapy now refuses to
+ run with a Python version lower than 3.5.2, which introduced
+ :class:`typing.Type` (:issue:`4615`)
+
+
+Deprecations
+~~~~~~~~~~~~
+
+* :meth:`TextResponse.body_as_unicode
+ ` is now deprecated, use
+ :attr:`TextResponse.text ` instead
+ (:issue:`4546`, :issue:`4555`, :issue:`4579`)
+
+* :class:`scrapy.item.BaseItem` is now deprecated, use
+ :class:`scrapy.item.Item` instead (:issue:`4534`)
+
+
+New features
+~~~~~~~~~~~~
+
+* :ref:`dataclass objects ` and
+ :ref:`attrs objects ` are now valid :ref:`item types
+ `, and a new itemadapter_ library makes it easy to
+ write code that :ref:`supports any item type `
+ (:issue:`2749`, :issue:`2807`, :issue:`3761`, :issue:`3881`, :issue:`4642`)
+
+* A new :meth:`TextResponse.json ` method
+ allows to deserialize JSON responses (:issue:`2444`, :issue:`4460`,
+ :issue:`4574`)
+
+* A new :signal:`bytes_received` signal allows monitoring response download
+ progress and :ref:`stopping downloads `
+ (:issue:`4205`, :issue:`4559`)
+
+* The dictionaries in the result list of a :ref:`media pipeline
+ ` now include a new key, ``status``, which indicates
+ if the file was downloaded or, if the file was not downloaded, why it was
+ not downloaded; see :meth:`FilesPipeline.get_media_requests
+ ` for more
+ information (:issue:`2893`, :issue:`4486`)
+
+* When using :ref:`Google Cloud Storage ` for
+ a :ref:`media pipeline `, a warning is now logged if
+ the configured credentials do not grant the required permissions
+ (:issue:`4346`, :issue:`4508`)
+
+* :ref:`Link extractors ` are now serializable,
+ as long as you do not use :ref:`lambdas ` for parameters; for
+ example, you can now pass link extractors in :attr:`Request.cb_kwargs
+ ` or
+ :attr:`Request.meta ` when :ref:`persisting
+ scheduled requests ` (:issue:`4554`)
+
+* Upgraded the :ref:`pickle protocol ` that Scrapy uses
+ from protocol 2 to protocol 4, improving serialization capabilities and
+ performance (:issue:`4135`, :issue:`4541`)
+
+* :func:`scrapy.utils.misc.create_instance` now raises a :exc:`TypeError`
+ exception if the resulting instance is ``None`` (:issue:`4528`,
+ :issue:`4532`)
+
+.. _itemadapter: https://github.com/scrapy/itemadapter
+
+
+Bug fixes
+~~~~~~~~~
+
+* :class:`~scrapy.downloadermiddlewares.cookies.CookiesMiddleware` no longer
+ discards cookies defined in :attr:`Request.headers
+ ` (:issue:`1992`, :issue:`2400`)
+
+* :class:`~scrapy.downloadermiddlewares.cookies.CookiesMiddleware` no longer
+ re-encodes cookies defined as :class:`bytes` in the ``cookies`` parameter
+ of the ``__init__`` method of :class:`~scrapy.http.Request`
+ (:issue:`2400`, :issue:`3575`)
+
+* When :setting:`FEEDS` defines multiple URIs, :setting:`FEED_STORE_EMPTY` is
+ ``False`` and the crawl yields no items, Scrapy no longer stops feed
+ exports after the first URI (:issue:`4621`, :issue:`4626`)
+
+* :class:`~scrapy.spiders.Spider` callbacks defined using :doc:`coroutine
+ syntax ` no longer need to return an iterable, and may
+ instead return a :class:`~scrapy.http.Request` object, an
+ :ref:`item `, or ``None`` (:issue:`4609`)
+
+* The :command:`startproject` command now ensures that the generated project
+ folders and files have the right permissions (:issue:`4604`)
+
+* Fix a :exc:`KeyError` exception being sometimes raised from
+ :class:`scrapy.utils.datatypes.LocalWeakReferencedCache` (:issue:`4597`,
+ :issue:`4599`)
+
+* When :setting:`FEEDS` defines multiple URIs, log messages about items being
+ stored now contain information from the corresponding feed, instead of
+ always containing information about only one of the feeds (:issue:`4619`,
+ :issue:`4629`)
+
+
+Documentation
+~~~~~~~~~~~~~
+
+* Added a new section about :ref:`accessing cb_kwargs from errbacks
+ ` (:issue:`4598`, :issue:`4634`)
+
+* Covered chompjs_ in :ref:`topics-parsing-javascript` (:issue:`4556`,
+ :issue:`4562`)
+
+* Removed from :doc:`topics/coroutines` the warning about the API being
+ experimental (:issue:`4511`, :issue:`4513`)
+
+* Removed references to unsupported versions of :doc:`Twisted
+ ` (:issue:`4533`)
+
+* Updated the description of the :ref:`screenshot pipeline example
+ `, which now uses :doc:`coroutine syntax
+ ` instead of returning a
+ :class:`~twisted.internet.defer.Deferred` (:issue:`4514`, :issue:`4593`)
+
+* Removed a misleading import line from the
+ :func:`scrapy.utils.log.configure_logging` code example (:issue:`4510`,
+ :issue:`4587`)
+
+* The display-on-hover behavior of internal documentation references now also
+ covers links to :ref:`commands `, :attr:`Request.meta
+ ` keys, :ref:`settings ` and
+ :ref:`signals ` (:issue:`4495`, :issue:`4563`)
+
+* It is again possible to download the documentation for offline reading
+ (:issue:`4578`, :issue:`4585`)
+
+* Removed backslashes preceding ``*args`` and ``**kwargs`` in some function
+ and method signatures (:issue:`4592`, :issue:`4596`)
+
+.. _chompjs: https://github.com/Nykakin/chompjs
+
+
+Quality assurance
+~~~~~~~~~~~~~~~~~
+
+* Adjusted the code base further to our :ref:`style guidelines
+ ` (:issue:`4237`, :issue:`4525`, :issue:`4538`,
+ :issue:`4539`, :issue:`4540`, :issue:`4542`, :issue:`4543`, :issue:`4544`,
+ :issue:`4545`, :issue:`4557`, :issue:`4558`, :issue:`4566`, :issue:`4568`,
+ :issue:`4572`)
+
+* Removed remnants of Python 2 support (:issue:`4550`, :issue:`4553`,
+ :issue:`4568`)
+
+* Improved code sharing between the :command:`crawl` and :command:`runspider`
+ commands (:issue:`4548`, :issue:`4552`)
+
+* Replaced ``chain(*iterable)`` with ``chain.from_iterable(iterable)``
+ (:issue:`4635`)
+
+* You may now run the :mod:`asyncio` tests with Tox on any Python version
+ (:issue:`4521`)
+
+* Updated test requirements to reflect an incompatibility with pytest 5.4 and
+ 5.4.1 (:issue:`4588`)
+
+* Improved :class:`~scrapy.spiderloader.SpiderLoader` test coverage for
+ scenarios involving duplicate spider names (:issue:`4549`, :issue:`4560`)
+
+* Configured Travis CI to also run the tests with Python 3.5.2
+ (:issue:`4518`, :issue:`4615`)
+
+* Added a `Pylint `_ job to Travis CI
+ (:issue:`3727`)
+
+* Added a `Mypy `_ job to Travis CI (:issue:`4637`)
+
+* Made use of set literals in tests (:issue:`4573`)
+
+* Cleaned up the Travis CI configuration (:issue:`4517`, :issue:`4519`,
+ :issue:`4522`, :issue:`4537`)
+
+
.. _release-2.1.0:
Scrapy 2.1.0 (2020-04-24)
@@ -778,6 +1619,8 @@ Deprecation removals
* ``scrapy.xlib`` has been removed (:issue:`4015`)
+.. _1.8-deprecations:
+
Deprecations
~~~~~~~~~~~~
@@ -1134,6 +1977,8 @@ The following deprecated settings have also been removed (:issue:`3578`):
* ``SPIDER_MANAGER_CLASS`` (use :setting:`SPIDER_LOADER_CLASS`)
+.. _1.7-deprecations:
+
Deprecations
~~~~~~~~~~~~
@@ -1773,7 +2618,7 @@ Bug fixes
- Fix compatibility with Twisted 17+ (:issue:`2496`, :issue:`2528`).
- Fix ``scrapy.Item`` inheritance on Python 3.6 (:issue:`2511`).
- Enforce numeric values for components order in ``SPIDER_MIDDLEWARES``,
- ``DOWNLOADER_MIDDLEWARES``, ``EXTENIONS`` and ``SPIDER_CONTRACTS`` (:issue:`2420`).
+ ``DOWNLOADER_MIDDLEWARES``, ``EXTENSIONS`` and ``SPIDER_CONTRACTS`` (:issue:`2420`).
Documentation
~~~~~~~~~~~~~
@@ -3529,7 +4374,7 @@ API changes
- ``url`` and ``body`` attributes of Request objects are now read-only (#230)
- ``Request.copy()`` and ``Request.replace()`` now also copies their ``callback`` and ``errback`` attributes (#231)
- Removed ``UrlFilterMiddleware`` from ``scrapy.contrib`` (already disabled by default)
-- Offsite middelware doesn't filter out any request coming from a spider that doesn't have a allowed_domains attribute (#225)
+- Offsite middleware doesn't filter out any request coming from a spider that doesn't have a allowed_domains attribute (#225)
- Removed Spider Manager ``load()`` method. Now spiders are loaded in the ``__init__`` method itself.
- Changes to Scrapy Manager (now called "Crawler"):
- ``scrapy.core.manager.ScrapyManager`` class renamed to ``scrapy.crawler.Crawler``
@@ -3676,13 +4521,14 @@ First release of Scrapy.
.. _resource: https://docs.python.org/2/library/resource.html
.. _robots.txt: https://www.robotstxt.org/
.. _scrapely: https://github.com/scrapy/scrapely
+.. _scrapy-bench: https://github.com/scrapy/scrapy-bench
.. _service_identity: https://service-identity.readthedocs.io/en/stable/
.. _six: https://six.readthedocs.io/
.. _tox: https://pypi.org/project/tox/
.. _Twisted: https://twistedmatrix.com/trac/
-.. _Twisted - hello, asynchronous programming: http://jessenoller.com/blog/2009/02/11/twisted-hello-asynchronous-programming/
.. _w3lib: https://github.com/scrapy/w3lib
.. _w3lib.encoding: https://github.com/scrapy/w3lib/blob/master/w3lib/encoding.py
.. _What is cacheable: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
.. _zope.interface: https://zopeinterface.readthedocs.io/en/latest/
.. _Zsh: https://www.zsh.org/
+.. _zstandard: https://pypi.org/project/zstandard/
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 3d34b47da..a0930ba1e 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,4 @@
Sphinx>=3.0
sphinx-hoverxref>=0.2b1
sphinx-notfound-page>=0.4
-sphinx_rtd_theme>=0.4
+sphinx-rtd-theme>=0.5.2
\ No newline at end of file
diff --git a/docs/topics/api.rst b/docs/topics/api.rst
index 52509ffdf..900b19c7a 100644
--- a/docs/topics/api.rst
+++ b/docs/topics/api.rst
@@ -4,8 +4,6 @@
Core API
========
-.. versionadded:: 0.15
-
This section documents the Scrapy core API, and it's intended for developers of
extensions and middlewares.
@@ -31,7 +29,7 @@ how you :ref:`configure the downloader middlewares
.. class:: Crawler(spidercls, settings)
The Crawler object must be instantiated with a
- :class:`scrapy.spiders.Spider` subclass and a
+ :class:`scrapy.Spider` subclass and a
:class:`scrapy.settings.Settings` object.
.. attribute:: settings
@@ -198,7 +196,7 @@ SpiderLoader API
match the request's url against the domains of the spiders.
:param request: queried request
- :type request: :class:`~scrapy.http.Request` instance
+ :type request: :class:`~scrapy.Request` instance
.. _topics-api-signals:
diff --git a/docs/topics/architecture.rst b/docs/topics/architecture.rst
index ae25dfa2f..71d027c86 100644
--- a/docs/topics/architecture.rst
+++ b/docs/topics/architecture.rst
@@ -87,8 +87,9 @@ of the system, and triggering events when certain actions occur. See the
Scheduler
---------
-The Scheduler receives requests from the engine and enqueues them for feeding
-them later (also to the engine) when the engine requests them.
+The :ref:`scheduler ` receives requests from the engine and
+enqueues them for feeding them later (also to the engine) when the engine
+requests them.
.. _component-downloader:
@@ -104,7 +105,7 @@ Spiders
-------
Spiders are custom classes written by Scrapy users to parse responses and
-extract items (aka scraped items) from them or additional requests to
+extract :ref:`items ` from them or additional requests to
follow. For more information see :ref:`topics-spiders`.
.. _component-pipelines:
diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst
index 038a459fd..82c5f271f 100644
--- a/docs/topics/asyncio.rst
+++ b/docs/topics/asyncio.rst
@@ -1,16 +1,19 @@
+.. _using-asyncio:
+
=======
asyncio
=======
.. versionadded:: 2.0
-Scrapy has partial support :mod:`asyncio`. After you :ref:`install the asyncio
-reactor `, you may use :mod:`asyncio` and
+Scrapy has partial support for :mod:`asyncio`. After you :ref:`install the
+asyncio reactor `, you may use :mod:`asyncio` and
:mod:`asyncio`-powered libraries in any :doc:`coroutine `.
-.. warning:: :mod:`asyncio` support in Scrapy is experimental. Future Scrapy
- versions may introduce related changes without a deprecation
- period or warning.
+.. warning:: :mod:`asyncio` support in Scrapy is experimental, and not yet
+ recommended for production environments. Future Scrapy versions
+ may introduce related changes without a deprecation period or
+ warning.
.. _install-asyncio:
@@ -26,3 +29,15 @@ reactor manually. You can do that using
:func:`~scrapy.utils.reactor.install_reactor`::
install_reactor('twisted.internet.asyncioreactor.AsyncioSelectorReactor')
+
+.. _using-custom-loops:
+
+Using custom asyncio loops
+==========================
+
+You can also use custom asyncio event loops with the asyncio reactor. Set the
+:setting:`ASYNCIO_EVENT_LOOP` setting to the import path of the desired event loop class to
+use it instead of the default asyncio event loop.
+
+
+
diff --git a/docs/topics/autothrottle.rst b/docs/topics/autothrottle.rst
index 4317019fc..8e6aae65c 100644
--- a/docs/topics/autothrottle.rst
+++ b/docs/topics/autothrottle.rst
@@ -128,8 +128,6 @@ The maximum download delay (in seconds) to be set in case of high latencies.
AUTOTHROTTLE_TARGET_CONCURRENCY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 1.1
-
Default: ``1.0``
Average number of requests Scrapy should be sending in parallel to remote
diff --git a/docs/topics/benchmarking.rst b/docs/topics/benchmarking.rst
index 99469ebf1..0643df6a6 100644
--- a/docs/topics/benchmarking.rst
+++ b/docs/topics/benchmarking.rst
@@ -4,8 +4,6 @@
Benchmarking
============
-.. versionadded:: 0.17
-
Scrapy comes with a simple benchmarking suite that spawns a local HTTP server
and crawls it at the maximum possible speed. The goal of this benchmarking is
to get an idea of how Scrapy performs in your hardware, in order to have a
@@ -83,5 +81,6 @@ follow links, any custom spider you write will probably do more stuff which
results in slower crawl rates. How slower depends on how much your spider does
and how well it's written.
-In the future, more cases will be added to the benchmarking suite to cover
-other common scenarios.
+Use scrapy-bench_ for more complex benchmarking.
+
+.. _scrapy-bench: https://github.com/scrapy/scrapy-bench
\ No newline at end of file
diff --git a/docs/topics/commands.rst b/docs/topics/commands.rst
index a0dcba90d..eef6b36ff 100644
--- a/docs/topics/commands.rst
+++ b/docs/topics/commands.rst
@@ -6,8 +6,6 @@
Command line tool
=================
-.. versionadded:: 0.10
-
Scrapy is controlled through the ``scrapy`` command-line tool, to be referred
here as the "Scrapy tool" to differentiate it from the sub-commands, which we
just call "commands" or "Scrapy commands".
@@ -468,7 +466,7 @@ Supported options:
* ``--callback`` or ``-c``: spider method to use as callback for parsing the
response
-* ``--meta`` or ``-m``: additional request meta that will be passed to the callback
+* ``--meta`` or ``-m``: additional request meta that will be passed to the callback
request. This must be a valid json string. Example: --meta='{"foo" : "bar"}'
* ``--cbkwargs``: additional keyword arguments that will be passed to the callback.
@@ -491,6 +489,10 @@ Supported options:
* ``--verbose`` or ``-v``: display information for each depth level
+* ``--output`` or ``-o``: dump scraped items to a file
+
+ .. versionadded:: 2.3
+
.. skip: start
Usage example::
@@ -562,8 +564,6 @@ and Platform info, which is useful for bug reports.
bench
-----
-.. versionadded:: 0.17
-
* Syntax: ``scrapy bench``
* Requires project: *no*
@@ -598,8 +598,6 @@ Example:
Register commands via setup.py entry points
-------------------------------------------
-.. note:: This is an experimental feature, use with caution.
-
You can also add Scrapy commands from an external library by adding a
``scrapy.commands`` section in the entry points of the library ``setup.py``
file.
diff --git a/docs/topics/contracts.rst b/docs/topics/contracts.rst
index b8b3078c4..ef296dc9e 100644
--- a/docs/topics/contracts.rst
+++ b/docs/topics/contracts.rst
@@ -4,8 +4,6 @@
Spiders Contracts
=================
-.. versionadded:: 0.15
-
Testing spiders can get particularly annoying and while nothing prevents you
from writing unit tests the task gets cumbersome quickly. Scrapy offers an
integrated way of testing your spiders by the means of contracts.
@@ -39,7 +37,7 @@ This callback is tested using three built-in contracts:
.. class:: CallbackKeywordArgumentsContract
- This contract (``@cb_kwargs``) sets the :attr:`cb_kwargs `
+ This contract (``@cb_kwargs``) sets the :attr:`cb_kwargs `
attribute for the sample request. It must be a valid JSON dictionary.
::
@@ -81,7 +79,7 @@ override three methods:
.. class:: Contract(method, *args)
:param method: callback function to which the contract is associated
- :type method: function
+ :type method: collections.abc.Callable
:param args: list of arguments passed into the docstring (whitespace
separated)
@@ -90,7 +88,7 @@ override three methods:
.. method:: Contract.adjust_request_args(args)
This receives a ``dict`` as an argument containing default arguments
- for request object. :class:`~scrapy.http.Request` is used by default,
+ for request object. :class:`~scrapy.Request` is used by default,
but this can be changed with the ``request_cls`` attribute.
If multiple contracts in chain have this attribute defined, the last one is used.
diff --git a/docs/topics/coroutines.rst b/docs/topics/coroutines.rst
index 7a9ecd4d5..0904637b0 100644
--- a/docs/topics/coroutines.rst
+++ b/docs/topics/coroutines.rst
@@ -15,21 +15,16 @@ Supported callables
The following callables may be defined as coroutines using ``async def``, and
hence use coroutine syntax (e.g. ``await``, ``async for``, ``async with``):
-- :class:`~scrapy.http.Request` callbacks.
+- :class:`~scrapy.Request` callbacks.
- The following are known caveats of the current implementation that we aim
- to address in future versions of Scrapy:
-
- - The callback output is not processed until the whole callback finishes.
+ .. note:: The callback output is not processed until the whole callback
+ finishes.
As a side effect, if the callback raises an exception, none of its
output is processed.
- - Because `asynchronous generators were introduced in Python 3.6`_, you
- can only use ``yield`` if you are using Python 3.6 or later.
-
- If you need to output multiple items or requests and you are using
- Python 3.5, return an iterable (e.g. a list) instead.
+ This is a known caveat of the current implementation that we aim to
+ address in a future version of Scrapy.
- The :meth:`process_item` method of
:ref:`item pipelines `.
@@ -44,8 +39,6 @@ hence use coroutine syntax (e.g. ``await``, ``async for``, ``async with``):
- :ref:`Signal handlers that support deferreds `.
-.. _asynchronous generators were introduced in Python 3.6: https://www.python.org/dev/peps/pep-0525/
-
Usage
=====
@@ -53,21 +46,28 @@ There are several use cases for coroutines in Scrapy. Code that would
return Deferreds when written for previous Scrapy versions, such as downloader
middlewares and signal handlers, can be rewritten to be shorter and cleaner::
+ from itemadapter import ItemAdapter
+
class DbPipeline:
def _update_item(self, data, item):
- item['field'] = data
+ adapter = ItemAdapter(item)
+ adapter['field'] = data
return item
def process_item(self, item, spider):
- dfd = db.get_some_data(item['id'])
+ adapter = ItemAdapter(item)
+ dfd = db.get_some_data(adapter['id'])
dfd.addCallback(self._update_item, item)
return dfd
becomes::
+ from itemadapter import ItemAdapter
+
class DbPipeline:
async def process_item(self, item, spider):
- item['field'] = await db.get_some_data(item['id'])
+ adapter = ItemAdapter(item)
+ adapter['field'] = await db.get_some_data(adapter['id'])
return item
Coroutines may be used to call asynchronous code. This includes other
diff --git a/docs/topics/debug.rst b/docs/topics/debug.rst
index d75f17301..4d452b4df 100644
--- a/docs/topics/debug.rst
+++ b/docs/topics/debug.rst
@@ -36,7 +36,7 @@ Consider the following Scrapy spider below::
Basically this is a simple spider which parses two pages of items (the
start_urls). Items also have a details page with additional information, so we
-use the ``cb_kwargs`` functionality of :class:`~scrapy.http.Request` to pass a
+use the ``cb_kwargs`` functionality of :class:`~scrapy.Request` to pass a
partially populated item.
diff --git a/docs/topics/deploy.rst b/docs/topics/deploy.rst
index 361914a29..961d6dc01 100644
--- a/docs/topics/deploy.rst
+++ b/docs/topics/deploy.rst
@@ -14,7 +14,7 @@ spiders come in.
Popular choices for deploying Scrapy spiders are:
* :ref:`Scrapyd ` (open source)
-* :ref:`Scrapy Cloud ` (cloud-based)
+* :ref:`Zyte Scrapy Cloud ` (cloud-based)
.. _deploy-scrapyd:
@@ -32,28 +32,28 @@ Scrapyd is maintained by some of the Scrapy developers.
.. _deploy-scrapy-cloud:
-Deploying to Scrapy Cloud
-=========================
+Deploying to Zyte Scrapy Cloud
+==============================
-`Scrapy Cloud`_ is a hosted, cloud-based service by `Scrapinghub`_,
-the company behind Scrapy.
+`Zyte Scrapy Cloud`_ is a hosted, cloud-based service by Zyte_, the company
+behind Scrapy.
-Scrapy Cloud removes the need to setup and monitor servers
-and provides a nice UI to manage spiders and review scraped items,
-logs and stats.
+Zyte Scrapy Cloud removes the need to setup and monitor servers and provides a
+nice UI to manage spiders and review scraped items, logs and stats.
-To deploy spiders to Scrapy Cloud you can use the `shub`_ command line tool.
-Please refer to the `Scrapy Cloud documentation`_ for more information.
+To deploy spiders to Zyte Scrapy Cloud you can use the `shub`_ command line
+tool.
+Please refer to the `Zyte Scrapy Cloud documentation`_ for more information.
-Scrapy Cloud is compatible with Scrapyd and one can switch between
+Zyte Scrapy Cloud is compatible with Scrapyd and one can switch between
them as needed - the configuration is read from the ``scrapy.cfg`` file
just like ``scrapyd-deploy``.
-.. _Scrapyd: https://github.com/scrapy/scrapyd
.. _Deploying your project: https://scrapyd.readthedocs.io/en/latest/deploy.html
-.. _Scrapy Cloud: https://scrapinghub.com/scrapy-cloud
+.. _Scrapyd: https://github.com/scrapy/scrapyd
.. _scrapyd-client: https://github.com/scrapy/scrapyd-client
-.. _shub: https://doc.scrapinghub.com/shub.html
.. _scrapyd-deploy documentation: https://scrapyd.readthedocs.io/en/latest/deploy.html
-.. _Scrapy Cloud documentation: https://doc.scrapinghub.com/scrapy-cloud.html
-.. _Scrapinghub: https://scrapinghub.com/
+.. _shub: https://shub.readthedocs.io/en/latest/
+.. _Zyte: https://zyte.com/
+.. _Zyte Scrapy Cloud: https://www.zyte.com/scrapy-cloud/
+.. _Zyte Scrapy Cloud documentation: https://docs.zyte.com/scrapy-cloud.html
diff --git a/docs/topics/developer-tools.rst b/docs/topics/developer-tools.rst
index 4e87a00f2..057b1ec62 100644
--- a/docs/topics/developer-tools.rst
+++ b/docs/topics/developer-tools.rst
@@ -5,9 +5,9 @@ Using your browser's Developer Tools for scraping
=================================================
Here is a general guide on how to use your browser's Developer Tools
-to ease the scraping process. Today almost all browsers come with
+to ease the scraping process. Today almost all browsers come with
built in `Developer Tools`_ and although we will use Firefox in this
-guide, the concepts are applicable to any other browser.
+guide, the concepts are applicable to any other browser.
In this guide we'll introduce the basic tools to use from a browser's
Developer Tools by scraping `quotes.toscrape.com`_.
@@ -41,16 +41,16 @@ Therefore, you should keep in mind the following things:
Inspecting a website
====================
-By far the most handy feature of the Developer Tools is the `Inspector`
-feature, which allows you to inspect the underlying HTML code of
-any webpage. To demonstrate the Inspector, let's look at the
+By far the most handy feature of the Developer Tools is the `Inspector`
+feature, which allows you to inspect the underlying HTML code of
+any webpage. To demonstrate the Inspector, let's look at the
`quotes.toscrape.com`_-site.
On the site we have a total of ten quotes from various authors with specific
-tags, as well as the Top Ten Tags. Let's say we want to extract all the quotes
-on this page, without any meta-information about authors, tags, etc.
+tags, as well as the Top Ten Tags. Let's say we want to extract all the quotes
+on this page, without any meta-information about authors, tags, etc.
-Instead of viewing the whole source code for the page, we can simply right click
+Instead of viewing the whole source code for the page, we can simply right click
on a quote and select ``Inspect Element (Q)``, which opens up the `Inspector`.
In it you should see something like this:
@@ -97,16 +97,16 @@ Then, back to your web browser, right-click on the ``span`` tag, select
>>> response.xpath('/html/body/div/div[2]/div[1]/div[1]/span[1]/text()').getall()
['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”']
-Adding ``text()`` at the end we are able to extract the first quote with this
+Adding ``text()`` at the end we are able to extract the first quote with this
basic selector. But this XPath is not really that clever. All it does is
-go down a desired path in the source code starting from ``html``. So let's
-see if we can refine our XPath a bit:
+go down a desired path in the source code starting from ``html``. So let's
+see if we can refine our XPath a bit:
-If we check the `Inspector` again we'll see that directly beneath our
-expanded ``div`` tag we have nine identical ``div`` tags, each with the
-same attributes as our first. If we expand any of them, we'll see the same
+If we check the `Inspector` again we'll see that directly beneath our
+expanded ``div`` tag we have nine identical ``div`` tags, each with the
+same attributes as our first. If we expand any of them, we'll see the same
structure as with our first quote: Two ``span`` tags and one ``div`` tag. We can
-expand each ``span`` tag with the ``class="text"`` inside our ``div`` tags and
+expand each ``span`` tag with the ``class="text"`` inside our ``div`` tags and
see each quote:
.. code-block:: html
@@ -121,7 +121,7 @@ see each quote:
With this knowledge we can refine our XPath: Instead of a path to follow,
-we'll simply select all ``span`` tags with the ``class="text"`` by using
+we'll simply select all ``span`` tags with the ``class="text"`` by using
the `has-class-extension`_:
>>> response.xpath('//span[has-class("text")]/text()').getall()
@@ -130,45 +130,45 @@ the `has-class-extension`_:
'“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
...]
-And with one simple, cleverer XPath we are able to extract all quotes from
-the page. We could have constructed a loop over our first XPath to increase
-the number of the last ``div``, but this would have been unnecessarily
+And with one simple, cleverer XPath we are able to extract all quotes from
+the page. We could have constructed a loop over our first XPath to increase
+the number of the last ``div``, but this would have been unnecessarily
complex and by simply constructing an XPath with ``has-class("text")``
-we were able to extract all quotes in one line.
+we were able to extract all quotes in one line.
-The `Inspector` has a lot of other helpful features, such as searching in the
+The `Inspector` has a lot of other helpful features, such as searching in the
source code or directly scrolling to an element you selected. Let's demonstrate
-a use case:
+a use case:
-Say you want to find the ``Next`` button on the page. Type ``Next`` into the
-search bar on the top right of the `Inspector`. You should get two results.
-The first is a ``li`` tag with the ``class="next"``, the second the text
+Say you want to find the ``Next`` button on the page. Type ``Next`` into the
+search bar on the top right of the `Inspector`. You should get two results.
+The first is a ``li`` tag with the ``class="next"``, the second the text
of an ``a`` tag. Right click on the ``a`` tag and select ``Scroll into View``.
If you hover over the tag, you'll see the button highlighted. From here
-we could easily create a :ref:`Link Extractor ` to
-follow the pagination. On a simple site such as this, there may not be
+we could easily create a :ref:`Link Extractor ` to
+follow the pagination. On a simple site such as this, there may not be
the need to find an element visually but the ``Scroll into View`` function
-can be quite useful on complex sites.
+can be quite useful on complex sites.
Note that the search bar can also be used to search for and test CSS
-selectors. For example, you could search for ``span.text`` to find
-all quote texts. Instead of a full text search, this searches for
-exactly the ``span`` tag with the ``class="text"`` in the page.
+selectors. For example, you could search for ``span.text`` to find
+all quote texts. Instead of a full text search, this searches for
+exactly the ``span`` tag with the ``class="text"`` in the page.
.. _topics-network-tool:
The Network-tool
================
While scraping you may come across dynamic webpages where some parts
-of the page are loaded dynamically through multiple requests. While
-this can be quite tricky, the `Network`-tool in the Developer Tools
+of the page are loaded dynamically through multiple requests. While
+this can be quite tricky, the `Network`-tool in the Developer Tools
greatly facilitates this task. To demonstrate the Network-tool, let's
-take a look at the page `quotes.toscrape.com/scroll`_.
+take a look at the page `quotes.toscrape.com/scroll`_.
-The page is quite similar to the basic `quotes.toscrape.com`_-page,
-but instead of the above-mentioned ``Next`` button, the page
-automatically loads new quotes when you scroll to the bottom. We
-could go ahead and try out different XPaths directly, but instead
+The page is quite similar to the basic `quotes.toscrape.com`_-page,
+but instead of the above-mentioned ``Next`` button, the page
+automatically loads new quotes when you scroll to the bottom. We
+could go ahead and try out different XPaths directly, but instead
we'll check another quite useful command from the Scrapy shell:
.. skip: next
@@ -179,9 +179,9 @@ we'll check another quite useful command from the Scrapy shell:
(...)
>>> view(response)
-A browser window should open with the webpage but with one
-crucial difference: Instead of the quotes we just see a greenish
-bar with the word ``Loading...``.
+A browser window should open with the webpage but with one
+crucial difference: Instead of the quotes we just see a greenish
+bar with the word ``Loading...``.
.. image:: _images/network_01.png
:width: 777
@@ -189,21 +189,21 @@ bar with the word ``Loading...``.
:alt: Response from quotes.toscrape.com/scroll
The ``view(response)`` command let's us view the response our
-shell or later our spider receives from the server. Here we see
-that some basic template is loaded which includes the title,
+shell or later our spider receives from the server. Here we see
+that some basic template is loaded which includes the title,
the login-button and the footer, but the quotes are missing. This
tells us that the quotes are being loaded from a different request
-than ``quotes.toscrape/scroll``.
+than ``quotes.toscrape/scroll``.
-If you click on the ``Network`` tab, you will probably only see
-two entries. The first thing we do is enable persistent logs by
-clicking on ``Persist Logs``. If this option is disabled, the
+If you click on the ``Network`` tab, you will probably only see
+two entries. The first thing we do is enable persistent logs by
+clicking on ``Persist Logs``. If this option is disabled, the
log is automatically cleared each time you navigate to a different
-page. Enabling this option is a good default, since it gives us
-control on when to clear the logs.
+page. Enabling this option is a good default, since it gives us
+control on when to clear the logs.
If we reload the page now, you'll see the log get populated with six
-new requests.
+new requests.
.. image:: _images/network_02.png
:width: 777
@@ -212,31 +212,31 @@ new requests.
Here we see every request that has been made when reloading the page
and can inspect each request and its response. So let's find out
-where our quotes are coming from:
+where our quotes are coming from:
-First click on the request with the name ``scroll``. On the right
+First click on the request with the name ``scroll``. On the right
you can now inspect the request. In ``Headers`` you'll find details
about the request headers, such as the URL, the method, the IP-address,
and so on. We'll ignore the other tabs and click directly on ``Response``.
-What you should see in the ``Preview`` pane is the rendered HTML-code,
-that is exactly what we saw when we called ``view(response)`` in the
-shell. Accordingly the ``type`` of the request in the log is ``html``.
-The other requests have types like ``css`` or ``js``, but what
-interests us is the one request called ``quotes?page=1`` with the
-type ``json``.
+What you should see in the ``Preview`` pane is the rendered HTML-code,
+that is exactly what we saw when we called ``view(response)`` in the
+shell. Accordingly the ``type`` of the request in the log is ``html``.
+The other requests have types like ``css`` or ``js``, but what
+interests us is the one request called ``quotes?page=1`` with the
+type ``json``.
-If we click on this request, we see that the request URL is
+If we click on this request, we see that the request URL is
``http://quotes.toscrape.com/api/quotes?page=1`` and the response
is a JSON-object that contains our quotes. We can also right-click
-on the request and open ``Open in new tab`` to get a better overview.
+on the request and open ``Open in new tab`` to get a better overview.
.. image:: _images/network_03.png
:width: 777
:height: 375
:alt: JSON-object returned from the quotes.toscrape API
-With this response we can now easily parse the JSON-object and
+With this response we can now easily parse the JSON-object and
also request each page to get every quote on the site::
import scrapy
@@ -255,17 +255,17 @@ also request each page to get every quote on the site::
yield {"quote": quote["text"]}
if data["has_next"]:
self.page += 1
- url = "http://quotes.toscrape.com/api/quotes?page={}".format(self.page)
+ url = f"http://quotes.toscrape.com/api/quotes?page={self.page}"
yield scrapy.Request(url=url, callback=self.parse)
-This spider starts at the first page of the quotes-API. With each
-response, we parse the ``response.text`` and assign it to ``data``.
-This lets us operate on the JSON-object like on a Python dictionary.
+This spider starts at the first page of the quotes-API. With each
+response, we parse the ``response.text`` and assign it to ``data``.
+This lets us operate on the JSON-object like on a Python dictionary.
We iterate through the ``quotes`` and print out the ``quote["text"]``.
-If the handy ``has_next`` element is ``true`` (try loading
+If the handy ``has_next`` element is ``true`` (try loading
`quotes.toscrape.com/api/quotes?page=10`_ in your browser or a
-page-number greater than 10), we increment the ``page`` attribute
-and ``yield`` a new request, inserting the incremented page-number
+page-number greater than 10), we increment the ``page`` attribute
+and ``yield`` a new request, inserting the incremented page-number
into our ``url``.
.. _requests-from-curl:
@@ -274,7 +274,7 @@ In more complex websites, it could be difficult to easily reproduce the
requests, as we could need to add ``headers`` or ``cookies`` to make it work.
In those cases you can export the requests in `cURL `_
format, by right-clicking on each of them in the network tool and using the
-:meth:`~scrapy.http.Request.from_curl()` method to generate an equivalent
+:meth:`~scrapy.Request.from_curl()` method to generate an equivalent
request::
from scrapy import Request
@@ -289,14 +289,16 @@ request::
"://quotes.toscrape.com/scroll' -H 'Cache-Control: max-age=0'")
Alternatively, if you want to know the arguments needed to recreate that
-request you can use the :func:`scrapy.utils.curl.curl_to_request_kwargs`
-function to get a dictionary with the equivalent arguments.
+request you can use the :func:`~scrapy.utils.curl.curl_to_request_kwargs`
+function to get a dictionary with the equivalent arguments:
+
+.. autofunction:: scrapy.utils.curl.curl_to_request_kwargs
Note that to translate a cURL command into a Scrapy request,
you may use `curl2scrapy `_.
As you can see, with a few inspections in the `Network`-tool we
-were able to easily replicate the dynamic requests of the scrolling
+were able to easily replicate the dynamic requests of the scrolling
functionality of the page. Crawling dynamic pages can be quite
daunting and pages can be very complex, but it (mostly) boils down
to identifying the correct request and replicating it in your spider.
diff --git a/docs/topics/downloader-middleware.rst b/docs/topics/downloader-middleware.rst
index 323e553e5..80c6c2c37 100644
--- a/docs/topics/downloader-middleware.rst
+++ b/docs/topics/downloader-middleware.rst
@@ -76,7 +76,7 @@ object gives you access, for example, to the :ref:`settings `.
middleware.
:meth:`process_request` should either: return ``None``, return a
- :class:`~scrapy.http.Response` object, return a :class:`~scrapy.http.Request`
+ :class:`~scrapy.Response` object, return a :class:`~scrapy.http.Request`
object, or raise :exc:`~scrapy.exceptions.IgnoreRequest`.
If it returns ``None``, Scrapy will continue processing this request, executing all
@@ -88,7 +88,7 @@ object gives you access, for example, to the :ref:`settings `.
or the appropriate download function; it'll return that response. The :meth:`process_response`
methods of installed middleware is always called on every response.
- If it returns a :class:`~scrapy.http.Request` object, Scrapy will stop calling
+ If it returns a :class:`~scrapy.Request` object, Scrapy will stop calling
process_request methods and reschedule the returned request. Once the newly returned
request is performed, the appropriate middleware chain will be called on
the downloaded response.
@@ -100,22 +100,22 @@ object gives you access, for example, to the :ref:`settings `.
ignored and not logged (unlike other exceptions).
:param request: the request being processed
- :type request: :class:`~scrapy.http.Request` object
+ :type request: :class:`~scrapy.Request` object
:param spider: the spider for which this request is intended
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: process_response(request, response, spider)
:meth:`process_response` should either: return a :class:`~scrapy.http.Response`
- object, return a :class:`~scrapy.http.Request` object or
+ object, return a :class:`~scrapy.Request` object or
raise a :exc:`~scrapy.exceptions.IgnoreRequest` exception.
If it returns a :class:`~scrapy.http.Response` (it could be the same given
response, or a brand-new one), that response will continue to be processed
with the :meth:`process_response` of the next middleware in the chain.
- If it returns a :class:`~scrapy.http.Request` object, the middleware chain is
+ If it returns a :class:`~scrapy.Request` object, the middleware chain is
halted and the returned request is rescheduled to be downloaded in the future.
This is the same behavior as if a request is returned from :meth:`process_request`.
@@ -124,13 +124,13 @@ object gives you access, for example, to the :ref:`settings `.
exception, it is ignored and not logged (unlike other exceptions).
:param request: the request that originated the response
- :type request: is a :class:`~scrapy.http.Request` object
+ :type request: is a :class:`~scrapy.Request` object
:param response: the response being processed
:type response: :class:`~scrapy.http.Response` object
:param spider: the spider for which this response is intended
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: process_exception(request, exception, spider)
@@ -139,7 +139,7 @@ object gives you access, for example, to the :ref:`settings `.
exception (including an :exc:`~scrapy.exceptions.IgnoreRequest` exception)
:meth:`process_exception` should return: either ``None``,
- a :class:`~scrapy.http.Response` object, or a :class:`~scrapy.http.Request` object.
+ a :class:`~scrapy.http.Response` object, or a :class:`~scrapy.Request` object.
If it returns ``None``, Scrapy will continue processing this exception,
executing any other :meth:`process_exception` methods of installed middleware,
@@ -149,19 +149,19 @@ object gives you access, for example, to the :ref:`settings `.
method chain of installed middleware is started, and Scrapy won't bother calling
any other :meth:`process_exception` methods of middleware.
- If it returns a :class:`~scrapy.http.Request` object, the returned request is
+ If it returns a :class:`~scrapy.Request` object, the returned request is
rescheduled to be downloaded in the future. This stops the execution of
:meth:`process_exception` methods of the middleware the same as returning a
response would.
:param request: the request that generated the exception
- :type request: is a :class:`~scrapy.http.Request` object
+ :type request: is a :class:`~scrapy.Request` object
:param exception: the raised exception
:type exception: an ``Exception`` object
:param spider: the spider for which this request is intended
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: from_crawler(cls, crawler)
@@ -203,10 +203,15 @@ CookiesMiddleware
browsers do.
.. caution:: When non-UTF8 encoded byte sequences are passed to a
- :class:`~scrapy.http.Request`, the ``CookiesMiddleware`` will log
+ :class:`~scrapy.Request`, the ``CookiesMiddleware`` will log
a warning. Refer to :ref:`topics-logging-advanced-customization`
to customize the logging behaviour.
+ .. caution:: Cookies set via the ``Cookie`` header are not considered by the
+ :ref:`cookies-mw`. If you need to set cookies for a request, use the
+ :class:`Request.cookies ` parameter. This is a known
+ current limitation that is being worked on.
+
The following settings can be used to configure the cookie middleware:
* :setting:`COOKIES_ENABLED`
@@ -217,8 +222,6 @@ The following settings can be used to configure the cookie middleware:
Multiple cookie sessions per spider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 0.15
-
There is support for keeping multiple cookie sessions per spider by using the
:reqmeta:`cookiejar` Request meta key. By default it uses a single cookie jar
(session), but you can pass an identifier to use different ones.
@@ -255,7 +258,7 @@ web server and received cookies in :class:`~scrapy.http.Response` will
**not** be merged with the existing cookies.
For more detailed information see the ``cookies`` parameter in
-:class:`~scrapy.http.Request`.
+:class:`~scrapy.Request`.
.. setting:: COOKIES_DEBUG
@@ -475,8 +478,6 @@ DBM storage backend
.. class:: DbmCacheStorage
- .. versionadded:: 0.13
-
A DBM_ storage backend is also available for the HTTP cache middleware.
By default, it uses the :mod:`dbm`, but you can change it with the
@@ -500,7 +501,7 @@ defines the methods described below.
the :signal:`open_spider ` signal.
:param spider: the spider which has been opened
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: close_spider(spider)
@@ -508,27 +509,27 @@ defines the methods described below.
the :signal:`close_spider ` signal.
:param spider: the spider which has been closed
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: retrieve_response(spider, request)
Return response if present in cache, or ``None`` otherwise.
:param spider: the spider which generated the request
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
:param request: the request to find cached response for
- :type request: :class:`~scrapy.http.Request` object
+ :type request: :class:`~scrapy.Request` object
.. method:: store_response(spider, request, response)
Store the given response in the cache.
:param spider: the spider for which the response is intended
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
:param request: the corresponding request the spider generated
- :type request: :class:`~scrapy.http.Request` object
+ :type request: :class:`~scrapy.Request` object
:param response: the response to store in the cache
:type response: :class:`~scrapy.http.Response` object
@@ -549,15 +550,10 @@ settings:
HTTPCACHE_ENABLED
^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.11
-
Default: ``False``
Whether the HTTP cache will be enabled.
-.. versionchanged:: 0.11
- Before 0.11, :setting:`HTTPCACHE_DIR` was used to enable cache.
-
.. setting:: HTTPCACHE_EXPIRATION_SECS
HTTPCACHE_EXPIRATION_SECS
@@ -570,9 +566,6 @@ Expiration time for cached requests, in seconds.
Cached requests older than this time will be re-downloaded. If zero, cached
requests will never expire.
-.. versionchanged:: 0.11
- Before 0.11, zero meant cached requests always expire.
-
.. setting:: HTTPCACHE_DIR
HTTPCACHE_DIR
@@ -589,8 +582,6 @@ project data dir. For more info see: :ref:`topics-project-structure`.
HTTPCACHE_IGNORE_HTTP_CODES
^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.10
-
Default: ``[]``
Don't cache response with these HTTP codes.
@@ -609,8 +600,6 @@ If enabled, requests not found in the cache will be ignored instead of downloade
HTTPCACHE_IGNORE_SCHEMES
^^^^^^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.10
-
Default: ``['file']``
Don't cache responses with these URI schemes.
@@ -629,8 +618,6 @@ The class which implements the cache storage backend.
HTTPCACHE_DBM_MODULE
^^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.13
-
Default: ``'dbm'``
The database module to use in the :ref:`DBM storage backend
@@ -641,8 +628,6 @@ The database module to use in the :ref:`DBM storage backend
HTTPCACHE_POLICY
^^^^^^^^^^^^^^^^
-.. versionadded:: 0.18
-
Default: ``'scrapy.extensions.httpcache.DummyPolicy'``
The class which implements the cache policy.
@@ -652,8 +637,6 @@ The class which implements the cache policy.
HTTPCACHE_GZIP
^^^^^^^^^^^^^^
-.. versionadded:: 1.0
-
Default: ``False``
If enabled, will compress all cached data with gzip.
@@ -664,8 +647,6 @@ This setting is specific to the Filesystem backend.
HTTPCACHE_ALWAYS_STORE
^^^^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 1.1
-
Default: ``False``
If enabled, will cache pages unconditionally.
@@ -684,8 +665,6 @@ responses you feed to the cache middleware.
HTTPCACHE_IGNORE_RESPONSE_CACHE_CONTROLS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 1.1
-
Default: ``[]``
List of Cache-Control directives in responses to be ignored.
@@ -710,11 +689,14 @@ HttpCompressionMiddleware
This middleware allows compressed (gzip, deflate) traffic to be
sent/received from web sites.
- This middleware also supports decoding `brotli-compressed`_ responses,
- provided `brotlipy`_ is installed.
+ This middleware also supports decoding `brotli-compressed`_ as well as
+ `zstd-compressed`_ responses, provided that `brotlipy`_ or `zstandard`_ is
+ installed, respectively.
.. _brotli-compressed: https://www.ietf.org/rfc/rfc7932.txt
.. _brotlipy: https://pypi.org/project/brotlipy/
+.. _zstd-compressed: https://www.ietf.org/rfc/rfc8478.txt
+.. _zstandard: https://pypi.org/project/zstandard/
HttpCompressionMiddleware Settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -735,14 +717,12 @@ HttpProxyMiddleware
.. module:: scrapy.downloadermiddlewares.httpproxy
:synopsis: Http Proxy Middleware
-.. versionadded:: 0.8
-
.. reqmeta:: proxy
.. class:: HttpProxyMiddleware
This middleware sets the HTTP proxy to use for requests, by setting the
- ``proxy`` meta value for :class:`~scrapy.http.Request` objects.
+ ``proxy`` meta value for :class:`~scrapy.Request` objects.
Like the Python standard library module :mod:`urllib.request`, it obeys
the following environment variables:
@@ -769,12 +749,12 @@ RedirectMiddleware
.. reqmeta:: redirect_urls
The urls which the request goes through (while being redirected) can be found
-in the ``redirect_urls`` :attr:`Request.meta ` key.
+in the ``redirect_urls`` :attr:`Request.meta ` key.
.. reqmeta:: redirect_reasons
The reason behind each redirect in :reqmeta:`redirect_urls` can be found in the
-``redirect_reasons`` :attr:`Request.meta ` key. For
+``redirect_reasons`` :attr:`Request.meta ` key. For
example: ``[301, 302, 307, 'meta refresh']``.
The format of a reason depends on the middleware that handled the corresponding
@@ -790,7 +770,7 @@ settings (see the settings documentation for more info):
.. reqmeta:: dont_redirect
-If :attr:`Request.meta ` has ``dont_redirect``
+If :attr:`Request.meta ` has ``dont_redirect``
key set to True, the request will be ignored by this middleware.
If you want to handle some redirect status codes in your spider, you can
@@ -803,7 +783,7 @@ responses (and pass them through to your spider) you can do this::
handle_httpstatus_list = [301, 302]
The ``handle_httpstatus_list`` key of :attr:`Request.meta
-` can also be used to specify which response codes to
+` can also be used to specify which response codes to
allow on a per-request basis. You can also set the meta key
``handle_httpstatus_all`` to ``True`` if you want to allow any response code
for a request.
@@ -817,8 +797,6 @@ RedirectMiddleware settings
REDIRECT_ENABLED
^^^^^^^^^^^^^^^^
-.. versionadded:: 0.13
-
Default: ``True``
Whether the Redirect middleware will be enabled.
@@ -860,8 +838,6 @@ MetaRefreshMiddleware settings
METAREFRESH_ENABLED
^^^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.17
-
Default: ``True``
Whether the Meta Refresh middleware will be enabled.
@@ -913,9 +889,14 @@ settings (see the settings documentation for more info):
.. reqmeta:: dont_retry
-If :attr:`Request.meta ` has ``dont_retry`` key
+If :attr:`Request.meta ` has ``dont_retry`` key
set to True, the request will be ignored by this middleware.
+To retry requests from a spider callback, you can use the
+:func:`get_retry_request` function:
+
+.. autofunction:: get_retry_request
+
RetryMiddleware Settings
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -924,8 +905,6 @@ RetryMiddleware Settings
RETRY_ENABLED
^^^^^^^^^^^^^
-.. versionadded:: 0.13
-
Default: ``True``
Whether the Retry middleware will be enabled.
@@ -940,7 +919,7 @@ Default: ``2``
Maximum number of times to retry, in addition to the first download.
Maximum number of retries can also be specified per-request using
-:reqmeta:`max_retry_times` attribute of :attr:`Request.meta `.
+:reqmeta:`max_retry_times` attribute of :attr:`Request.meta `.
When initialized, the :reqmeta:`max_retry_times` meta key takes higher
precedence over the :setting:`RETRY_TIMES` setting.
@@ -958,6 +937,18 @@ In some cases you may want to add 400 to :setting:`RETRY_HTTP_CODES` because
it is a common code used to indicate server overload. It is not included by
default because HTTP specs say so.
+.. setting:: RETRY_PRIORITY_ADJUST
+
+RETRY_PRIORITY_ADJUST
+---------------------
+
+Default: ``-1``
+
+Adjust retry request priority relative to original request:
+
+- a positive priority adjust means higher priority.
+- **a negative priority adjust (default) means lower priority.**
+
.. _topics-dlmw-robots:
@@ -995,7 +986,7 @@ RobotsTxtMiddleware
.. reqmeta:: dont_obey_robotstxt
-If :attr:`Request.meta ` has
+If :attr:`Request.meta ` has
``dont_obey_robotstxt`` key set to True
the request will be ignored by this middleware even if
:setting:`ROBOTSTXT_OBEY` is enabled.
@@ -1179,8 +1170,6 @@ AjaxCrawlMiddleware Settings
AJAXCRAWL_ENABLED
^^^^^^^^^^^^^^^^^
-.. versionadded:: 0.21
-
Default: ``False``
Whether the AjaxCrawlMiddleware will be enabled. You may want to
diff --git a/docs/topics/dynamic-content.rst b/docs/topics/dynamic-content.rst
index e918bc006..f96be0bbc 100644
--- a/docs/topics/dynamic-content.rst
+++ b/docs/topics/dynamic-content.rst
@@ -62,9 +62,9 @@ download the webpage with an HTTP client like curl_ or wget_ and see if the
information can be found in the response they get.
If they get a response with the desired data, modify your Scrapy
-:class:`~scrapy.http.Request` to match that of the other HTTP client. For
+:class:`~scrapy.Request` to match that of the other HTTP client. For
example, try using the same user-agent string (:setting:`USER_AGENT`) or the
-same :attr:`~scrapy.http.Request.headers`.
+same :attr:`~scrapy.Request.headers`.
If they also get a response without the desired data, you’ll need to take
steps to make your request more similar to that of the web browser. See
@@ -81,14 +81,14 @@ Use the :ref:`network tool ` of your web browser to see
how your web browser performs the desired request, and try to reproduce that
request with Scrapy.
-It might be enough to yield a :class:`~scrapy.http.Request` with the same HTTP
+It might be enough to yield a :class:`~scrapy.Request` with the same HTTP
method and URL. However, you may also need to reproduce the body, headers and
-form parameters (see :class:`~scrapy.http.FormRequest`) of that request.
+form parameters (see :class:`~scrapy.FormRequest`) of that request.
As all major browsers allow to export the requests in `cURL
`_ format, Scrapy incorporates the method
-:meth:`~scrapy.http.Request.from_curl()` to generate an equivalent
-:class:`~scrapy.http.Request` from a cURL command. To get more information
+:meth:`~scrapy.Request.from_curl()` to generate an equivalent
+:class:`~scrapy.Request` from a cURL command. To get more information
visit :ref:`request from curl ` inside the network
tool section.
@@ -125,7 +125,7 @@ data from it depends on the type of response:
If the desired data is inside HTML or XML code embedded within JSON data,
you can load that HTML or XML code into a
- :class:`~scrapy.selector.Selector` and then
+ :class:`~scrapy.Selector` and then
:ref:`use it ` as usual::
selector = Selector(data['html'])
diff --git a/docs/topics/email.rst b/docs/topics/email.rst
index e347c3a35..1a2bc6330 100644
--- a/docs/topics/email.rst
+++ b/docs/topics/email.rst
@@ -62,10 +62,10 @@ rest of the framework.
:type smtpport: int
:param smtptls: enforce using SMTP STARTTLS
- :type smtptls: boolean
+ :type smtptls: bool
:param smtpssl: enforce using a secure SSL connection
- :type smtpssl: boolean
+ :type smtpssl: bool
.. classmethod:: from_settings(settings)
@@ -79,14 +79,14 @@ rest of the framework.
Send email to the given recipients.
- :param to: the e-mail recipients
- :type to: str or list of str
+ :param to: the e-mail recipients as a string or as a list of strings
+ :type to: str or list
:param subject: the subject of the e-mail
:type subject: str
- :param cc: the e-mails to CC
- :type cc: str or list of str
+ :param cc: the e-mails to CC as a string or as a list of strings
+ :type cc: str or list
:param body: the e-mail body
:type body: str
@@ -96,7 +96,7 @@ rest of the framework.
appear on the e-mail's attachment, ``mimetype`` is the mimetype of the
attachment and ``file_object`` is a readable file object with the
contents of the attachment
- :type attachs: iterable
+ :type attachs: collections.abc.Iterable
:param mimetype: the MIME type of the e-mail
:type mimetype: str
diff --git a/docs/topics/exceptions.rst b/docs/topics/exceptions.rst
index 583a50ab8..9150ca7d9 100644
--- a/docs/topics/exceptions.rst
+++ b/docs/topics/exceptions.rst
@@ -64,10 +64,10 @@ NotConfigured
This exception can be raised by some components to indicate that they will
remain disabled. Those components include:
- * Extensions
- * Item pipelines
- * Downloader middlewares
- * Spider middlewares
+- Extensions
+- Item pipelines
+- Downloader middlewares
+- Spider middlewares
The exception must be raised in the component's ``__init__`` method.
@@ -85,8 +85,8 @@ StopDownload
.. exception:: StopDownload(fail=True)
-Raised from a :class:`~scrapy.signals.bytes_received` signal handler to
-indicate that no further bytes should be downloaded for a response.
+Raised from a :class:`~scrapy.signals.bytes_received` or :class:`~scrapy.signals.headers_received`
+signal handler to indicate that no further bytes should be downloaded for a response.
The ``fail`` boolean parameter controls which method will handle the resulting
response:
@@ -110,5 +110,6 @@ attribute.
``StopDownload(False)`` or ``StopDownload(True)`` will raise
a :class:`TypeError`.
-See the documentation for the :class:`~scrapy.signals.bytes_received` signal
+See the documentation for the :class:`~scrapy.signals.bytes_received` and
+:class:`~scrapy.signals.headers_received` signals
and the :ref:`topics-stop-response-download` topic for additional information and examples.
diff --git a/docs/topics/exporters.rst b/docs/topics/exporters.rst
index 7daf25ab3..8c30122b6 100644
--- a/docs/topics/exporters.rst
+++ b/docs/topics/exporters.rst
@@ -40,6 +40,7 @@ Here you can see an :doc:`Item Pipeline ` which uses multiple
Item Exporters to group scraped items to different files according to the
value of one of their fields::
+ from itemadapter import ItemAdapter
from scrapy.exporters import XmlItemExporter
class PerYearXmlExportPipeline:
@@ -49,17 +50,19 @@ value of one of their fields::
self.year_to_exporter = {}
def close_spider(self, spider):
- for exporter in self.year_to_exporter.values():
+ for exporter, xml_file in self.year_to_exporter.values():
exporter.finish_exporting()
+ xml_file.close()
def _exporter_for_item(self, item):
- year = item['year']
+ adapter = ItemAdapter(item)
+ year = adapter['year']
if year not in self.year_to_exporter:
- f = open('{}.xml'.format(year), 'wb')
- exporter = XmlItemExporter(f)
+ xml_file = open(f'{year}.xml', 'wb')
+ exporter = XmlItemExporter(xml_file)
exporter.start_exporting()
- self.year_to_exporter[year] = exporter
- return self.year_to_exporter[year]
+ self.year_to_exporter[year] = (exporter, xml_file)
+ return self.year_to_exporter[year][0]
def process_item(self, item, spider):
exporter = self._exporter_for_item(item)
@@ -87,7 +90,7 @@ described next.
1. Declaring a serializer in the field
--------------------------------------
-If you use :class:`~.Item` you can declare a serializer in the
+If you use :class:`~scrapy.Item` you can declare a serializer in the
:ref:`field metadata `. The serializer must be
a callable which receives a value and returns its serialized form.
@@ -96,7 +99,7 @@ Example::
import scrapy
def serialize_price(value):
- return '$ %s' % str(value)
+ return f'$ {str(value)}'
class Product(scrapy.Item):
name = scrapy.Field()
@@ -120,8 +123,8 @@ Example::
def serialize_field(self, field, name, value):
if field == 'price':
- return '$ %s' % str(value)
- return super(Product, self).serialize_field(field, name, value)
+ return f'$ {str(value)}'
+ return super().serialize_field(field, name, value)
.. _topics-exporters-reference:
@@ -164,12 +167,12 @@ BaseItemExporter
By default, this method looks for a serializer :ref:`declared in the item
field ` and returns the result of applying
that serializer to the value. If no serializer is found, it returns the
- value unchanged except for ``unicode`` values which are encoded to
- ``str`` using the encoding declared in the :attr:`encoding` attribute.
+ value unchanged.
- :param field: the field being serialized. If a raw dict is being
- exported (not :class:`~.Item`) *field* value is an empty dict.
- :type field: :class:`~scrapy.item.Field` object or an empty dict
+ :param field: the field being serialized. If the source :ref:`item object
+ ` does not define field metadata, *field* is an empty
+ :class:`dict`.
+ :type field: :class:`~scrapy.Field` object or a :class:`dict` instance
:param name: the name of the field being serialized
:type name: str
@@ -192,14 +195,17 @@ BaseItemExporter
.. attribute:: fields_to_export
- A list with the name of the fields that will be exported, or None if you
- want to export all fields. Defaults to None.
+ A list with the name of the fields that will be exported, or ``None`` if
+ you want to export all fields. Defaults to ``None``.
Some exporters (like :class:`CsvItemExporter`) respect the order of the
fields defined in this attribute.
- Some exporters may require fields_to_export list in order to export the
- data properly when spiders return dicts (not :class:`~Item` instances).
+ When using :ref:`item objects ` that do not expose all their
+ possible fields, exporters that do not support exporting a different
+ subset of fields per item will only export the fields found in the first
+ item exported. Use ``fields_to_export`` to define all the fields to be
+ exported.
.. attribute:: export_empty_fields
@@ -211,10 +217,7 @@ BaseItemExporter
.. attribute:: encoding
- The encoding that will be used to encode unicode values. This only
- affects unicode values (which are always serialized to str using this
- encoding). Other value types are passed unchanged to the specific
- serialization library.
+ The output character encoding.
.. attribute:: indent
@@ -238,7 +241,7 @@ XmlItemExporter
.. class:: XmlItemExporter(file, item_element='item', root_element='items', **kwargs)
- Exports Items in XML format to the specified file object.
+ Exports items in XML format to the specified file object.
:param file: the file-like object to use for exporting the data. Its ``write`` method should
accept ``bytes`` (a disk file opened in binary mode, a ``io.BytesIO`` object, etc)
@@ -290,9 +293,9 @@ XmlItemExporter
CsvItemExporter
---------------
-.. class:: CsvItemExporter(file, include_headers_line=True, join_multivalued=',', **kwargs)
+.. class:: CsvItemExporter(file, include_headers_line=True, join_multivalued=',', errors=None, **kwargs)
- Exports Items in CSV format to the given file-like object. If the
+ Exports items in CSV format to the given file-like object. If the
:attr:`fields_to_export` attribute is set, it will be used to define the
CSV columns and their order. The :attr:`export_empty_fields` attribute has
no effect on this exporter.
@@ -303,12 +306,17 @@ CsvItemExporter
:param include_headers_line: If enabled, makes the exporter output a header
line with the field names taken from
:attr:`BaseItemExporter.fields_to_export` or the first exported item fields.
- :type include_headers_line: boolean
+ :type include_headers_line: bool
:param join_multivalued: The char (or chars) that will be used for joining
multi-valued fields, if found.
:type include_headers_line: str
+ :param errors: The optional string that specifies how encoding and decoding
+ errors are to be handled. For more information see
+ :class:`io.TextIOWrapper`.
+ :type errors: str
+
The additional keyword arguments of this ``__init__`` method are passed to the
:class:`BaseItemExporter` ``__init__`` method, and the leftover arguments to the
:func:`csv.writer` function, so you can use any :func:`csv.writer` function
@@ -325,7 +333,7 @@ PickleItemExporter
.. class:: PickleItemExporter(file, protocol=0, **kwargs)
- Exports Items in pickle format to the given file-like object.
+ Exports items in pickle format to the given file-like object.
:param file: the file-like object to use for exporting the data. Its ``write`` method should
accept ``bytes`` (a disk file opened in binary mode, a ``io.BytesIO`` object, etc)
@@ -345,7 +353,7 @@ PprintItemExporter
.. class:: PprintItemExporter(file, **kwargs)
- Exports Items in pretty print format to the specified file object.
+ Exports items in pretty print format to the specified file object.
:param file: the file-like object to use for exporting the data. Its ``write`` method should
accept ``bytes`` (a disk file opened in binary mode, a ``io.BytesIO`` object, etc)
@@ -365,7 +373,7 @@ JsonItemExporter
.. class:: JsonItemExporter(file, **kwargs)
- Exports Items in JSON format to the specified file-like object, writing all
+ Exports items in JSON format to the specified file-like object, writing all
objects as a list of objects. The additional ``__init__`` method arguments are
passed to the :class:`BaseItemExporter` ``__init__`` method, and the leftover
arguments to the :class:`~json.JSONEncoder` ``__init__`` method, so you can use any
@@ -394,7 +402,7 @@ JsonLinesItemExporter
.. class:: JsonLinesItemExporter(file, **kwargs)
- Exports Items in JSON format to the specified file-like object, writing one
+ Exports items in JSON format to the specified file-like object, writing one
JSON-encoded item per line. The additional ``__init__`` method arguments are passed
to the :class:`BaseItemExporter` ``__init__`` method, and the leftover arguments to
the :class:`~json.JSONEncoder` ``__init__`` method, so you can use any
diff --git a/docs/topics/extensions.rst b/docs/topics/extensions.rst
index 0fc83e645..9e86fd0fe 100644
--- a/docs/topics/extensions.rst
+++ b/docs/topics/extensions.rst
@@ -7,8 +7,7 @@ Extensions
The extensions framework provides a mechanism for inserting your own
custom functionality into Scrapy.
-Extensions are just regular classes that are instantiated at Scrapy startup,
-when extensions are initialized.
+Extensions are just regular classes.
Extension settings
==================
@@ -27,8 +26,8 @@ Loading & activating extensions
===============================
Extensions are loaded and activated at startup by instantiating a single
-instance of the extension class. Therefore, all the extension initialization
-code must be performed in the class ``__init__`` method.
+instance of the extension class per spider being run. All the extension
+initialization code must be performed in the class ``__init__`` method.
To make an extension available, add it to the :setting:`EXTENSIONS` setting in
your Scrapy settings. In :setting:`EXTENSIONS`, each extension is represented
@@ -257,6 +256,12 @@ settings:
* :setting:`CLOSESPIDER_PAGECOUNT`
* :setting:`CLOSESPIDER_ERRORCOUNT`
+.. note::
+
+ When a certain closing condition is met, requests which are
+ currently in the downloader queue (up to :setting:`CONCURRENT_REQUESTS`
+ requests) are still processed.
+
.. setting:: CLOSESPIDER_TIMEOUT
CLOSESPIDER_TIMEOUT
@@ -279,8 +284,6 @@ Default: ``0``
An integer which specifies a number of items. If the spider scrapes more than
that amount and those items are passed by the item pipeline, the
spider will be closed with the reason ``closespider_itemcount``.
-Requests which are currently in the downloader queue (up to
-:setting:`CONCURRENT_REQUESTS` requests) are still processed.
If zero (or non set), spiders won't be closed by number of passed items.
.. setting:: CLOSESPIDER_PAGECOUNT
@@ -288,8 +291,6 @@ If zero (or non set), spiders won't be closed by number of passed items.
CLOSESPIDER_PAGECOUNT
"""""""""""""""""""""
-.. versionadded:: 0.11
-
Default: ``0``
An integer which specifies the maximum number of responses to crawl. If the spider
@@ -302,8 +303,6 @@ number of crawled responses.
CLOSESPIDER_ERRORCOUNT
""""""""""""""""""""""
-.. versionadded:: 0.11
-
Default: ``0``
An integer which specifies the maximum number of errors to receive before
diff --git a/docs/topics/feed-exports.rst b/docs/topics/feed-exports.rst
index 9e5968a29..216a8bc52 100644
--- a/docs/topics/feed-exports.rst
+++ b/docs/topics/feed-exports.rst
@@ -4,8 +4,6 @@
Feed exports
============
-.. versionadded:: 0.10
-
One of the most frequently required features when implementing scrapers is
being able to store the scraped data properly and, quite often, that means
generating an "export file" with the scraped data (commonly called "export
@@ -23,10 +21,10 @@ Serialization formats
For serializing the scraped data, the feed exports use the :ref:`Item exporters
`. These formats are supported out of the box:
- * :ref:`topics-feed-format-json`
- * :ref:`topics-feed-format-jsonlines`
- * :ref:`topics-feed-format-csv`
- * :ref:`topics-feed-format-xml`
+- :ref:`topics-feed-format-json`
+- :ref:`topics-feed-format-jsonlines`
+- :ref:`topics-feed-format-csv`
+- :ref:`topics-feed-format-xml`
But you can also extend the supported format through the
:setting:`FEED_EXPORTERS` setting.
@@ -36,54 +34,58 @@ But you can also extend the supported format through the
JSON
----
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``json``
- * Exporter used: :class:`~scrapy.exporters.JsonItemExporter`
- * See :ref:`this warning ` if you're using JSON with
- large feeds.
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``json``
+
+- Exporter used: :class:`~scrapy.exporters.JsonItemExporter`
+
+- See :ref:`this warning ` if you're using JSON with
+ large feeds.
.. _topics-feed-format-jsonlines:
JSON lines
----------
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``jsonlines``
- * Exporter used: :class:`~scrapy.exporters.JsonLinesItemExporter`
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``jsonlines``
+- Exporter used: :class:`~scrapy.exporters.JsonLinesItemExporter`
.. _topics-feed-format-csv:
CSV
---
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``csv``
- * Exporter used: :class:`~scrapy.exporters.CsvItemExporter`
- * To specify columns to export and their order use
- :setting:`FEED_EXPORT_FIELDS`. Other feed exporters can also use this
- option, but it is important for CSV because unlike many other export
- formats CSV uses a fixed header.
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``csv``
+
+- Exporter used: :class:`~scrapy.exporters.CsvItemExporter`
+
+- To specify columns to export and their order use
+ :setting:`FEED_EXPORT_FIELDS`. Other feed exporters can also use this
+ option, but it is important for CSV because unlike many other export
+ formats CSV uses a fixed header.
.. _topics-feed-format-xml:
XML
---
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``xml``
- * Exporter used: :class:`~scrapy.exporters.XmlItemExporter`
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``xml``
+- Exporter used: :class:`~scrapy.exporters.XmlItemExporter`
.. _topics-feed-format-pickle:
Pickle
------
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``pickle``
- * Exporter used: :class:`~scrapy.exporters.PickleItemExporter`
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``pickle``
+- Exporter used: :class:`~scrapy.exporters.PickleItemExporter`
.. _topics-feed-format-marshal:
Marshal
-------
- * Value for the ``format`` key in the :setting:`FEEDS` setting: ``marshal``
- * Exporter used: :class:`~scrapy.exporters.MarshalItemExporter`
+- Value for the ``format`` key in the :setting:`FEEDS` setting: ``marshal``
+- Exporter used: :class:`~scrapy.exporters.MarshalItemExporter`
.. _topics-feed-storage:
@@ -97,10 +99,11 @@ storage backend types which are defined by the URI scheme.
The storages backends supported out of the box are:
- * :ref:`topics-feed-storage-fs`
- * :ref:`topics-feed-storage-ftp`
- * :ref:`topics-feed-storage-s3` (requires botocore_)
- * :ref:`topics-feed-storage-stdout`
+- :ref:`topics-feed-storage-fs`
+- :ref:`topics-feed-storage-ftp`
+- :ref:`topics-feed-storage-s3` (requires botocore_)
+- :ref:`topics-feed-storage-gcs` (requires `google-cloud-storage`_)
+- :ref:`topics-feed-storage-stdout`
Some storage backends may be unavailable if the required external libraries are
not available. For example, the S3 backend is only available if the botocore_
@@ -115,8 +118,8 @@ Storage URI parameters
The storage URI can also contain parameters that get replaced when the feed is
being created. These parameters are:
- * ``%(time)s`` - gets replaced by a timestamp when the feed is being created
- * ``%(name)s`` - gets replaced by the spider name
+- ``%(time)s`` - gets replaced by a timestamp when the feed is being created
+- ``%(name)s`` - gets replaced by the spider name
Any other named parameter gets replaced by the spider attribute of the same
name. For example, ``%(site_id)s`` would get replaced by the ``spider.site_id``
@@ -124,13 +127,16 @@ attribute the moment the feed is being created.
Here are some examples to illustrate:
- * Store in FTP using one directory per spider:
+- Store in FTP using one directory per spider:
- * ``ftp://user:password@ftp.example.com/scraping/feeds/%(name)s/%(time)s.json``
+ - ``ftp://user:password@ftp.example.com/scraping/feeds/%(name)s/%(time)s.json``
- * Store in S3 using one directory per spider:
+- Store in S3 using one directory per spider:
- * ``s3://mybucket/scraping/feeds/%(name)s/%(time)s.json``
+ - ``s3://mybucket/scraping/feeds/%(name)s/%(time)s.json``
+
+.. note:: :ref:`Spider arguments ` become spider attributes, hence
+ they can also be used as storage URI parameters.
.. _topics-feed-storage-backends:
@@ -145,9 +151,9 @@ Local filesystem
The feeds are stored in the local filesystem.
- * URI scheme: ``file``
- * Example URI: ``file:///tmp/export.csv``
- * Required external libraries: none
+- URI scheme: ``file``
+- Example URI: ``file:///tmp/export.csv``
+- Required external libraries: none
Note that for the local filesystem storage (only) you can omit the scheme if
you specify an absolute path like ``/tmp/export.csv``. This only works on Unix
@@ -160,15 +166,18 @@ FTP
The feeds are stored in a FTP server.
- * URI scheme: ``ftp``
- * Example URI: ``ftp://user:pass@ftp.example.com/path/to/export.csv``
- * Required external libraries: none
+- URI scheme: ``ftp``
+- Example URI: ``ftp://user:pass@ftp.example.com/path/to/export.csv``
+- Required external libraries: none
FTP supports two different connection modes: `active or passive
`_. Scrapy uses the passive connection
mode by default. To use the active connection mode instead, set the
:setting:`FEED_STORAGE_FTP_ACTIVE` setting to ``True``.
+This storage backend uses :ref:`delayed file delivery `.
+
+
.. _topics-feed-storage-s3:
S3
@@ -176,23 +185,58 @@ S3
The feeds are stored on `Amazon S3`_.
- * URI scheme: ``s3``
- * Example URIs:
+- URI scheme: ``s3``
- * ``s3://mybucket/path/to/export.csv``
- * ``s3://aws_key:aws_secret@mybucket/path/to/export.csv``
+- Example URIs:
- * Required external libraries: `botocore`_
+ - ``s3://mybucket/path/to/export.csv``
+
+ - ``s3://aws_key:aws_secret@mybucket/path/to/export.csv``
+
+- Required external libraries: `botocore`_ >= 1.4.87
The AWS credentials can be passed as user/password in the URI, or they can be
passed through the following settings:
- * :setting:`AWS_ACCESS_KEY_ID`
- * :setting:`AWS_SECRET_ACCESS_KEY`
+- :setting:`AWS_ACCESS_KEY_ID`
+- :setting:`AWS_SECRET_ACCESS_KEY`
-You can also define a custom ACL for exported feeds using this setting:
+You can also define a custom ACL and custom endpoint for exported feeds using this setting:
+
+- :setting:`FEED_STORAGE_S3_ACL`
+- :setting:`AWS_ENDPOINT_URL`
+
+This storage backend uses :ref:`delayed file delivery `.
+
+
+.. _topics-feed-storage-gcs:
+
+Google Cloud Storage (GCS)
+--------------------------
+
+.. versionadded:: 2.3
+
+The feeds are stored on `Google Cloud Storage`_.
+
+- URI scheme: ``gs``
+
+- Example URIs:
+
+ - ``gs://mybucket/path/to/export.csv``
+
+- Required external libraries: `google-cloud-storage`_.
+
+For more information about authentication, please refer to `Google Cloud documentation `_.
+
+You can set a *Project ID* and *Access Control List (ACL)* through the following settings:
+
+- :setting:`FEED_STORAGE_GCS_ACL`
+- :setting:`GCS_PROJECT_ID`
+
+This storage backend uses :ref:`delayed file delivery `.
+
+.. _google-cloud-storage: https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-python
- * :setting:`FEED_STORAGE_S3_ACL`
.. _topics-feed-storage-stdout:
@@ -201,9 +245,68 @@ Standard output
The feeds are written to the standard output of the Scrapy process.
- * URI scheme: ``stdout``
- * Example URI: ``stdout:``
- * Required external libraries: none
+- URI scheme: ``stdout``
+- Example URI: ``stdout:``
+- Required external libraries: none
+
+
+.. _delayed-file-delivery:
+
+Delayed file delivery
+---------------------
+
+As indicated above, some of the described storage backends use delayed file
+delivery.
+
+These storage backends do not upload items to the feed URI as those items are
+scraped. Instead, Scrapy writes items into a temporary local file, and only
+once all the file contents have been written (i.e. at the end of the crawl) is
+that file uploaded to the feed URI.
+
+If you want item delivery to start earlier when using one of these storage
+backends, use :setting:`FEED_EXPORT_BATCH_ITEM_COUNT` to split the output items
+in multiple files, with the specified maximum item count per file. That way, as
+soon as a file reaches the maximum item count, that file is delivered to the
+feed URI, allowing item delivery to start way before the end of the crawl.
+
+.. _item-filter:
+
+Item filtering
+==============
+
+.. versionadded:: VERSION
+
+You can filter items that you want to allow for a particular feed by using the
+``item_classes`` option in :ref:`feeds options `. Only items of
+the specified types will be added to the feed.
+
+The ``item_classes`` option is implemented by the :class:`~scrapy.extensions.feedexport.ItemFilter`
+class, which is the default value of the ``item_filter`` :ref:`feed option `.
+
+You can create your own custom filtering class by implementing :class:`~scrapy.extensions.feedexport.ItemFilter`'s
+method ``accepts`` and taking ``feed_options`` as an argument.
+
+For instance::
+
+ class MyCustomFilter:
+
+ def __init__(self, feed_options):
+ self.feed_options = feed_options
+
+ def accepts(self, item):
+ if "field1" in item and item["field1"] == "expected_data":
+ return True
+ return False
+
+
+You can assign your custom filtering class to the ``item_filter`` :ref:`option of a feed `.
+See :setting:`FEEDS` for examples.
+
+ItemFilter
+----------
+
+.. autoclass:: scrapy.extensions.feedexport.ItemFilter
+ :members:
Settings
@@ -211,15 +314,16 @@ Settings
These are the settings used for configuring the feed exports:
- * :setting:`FEEDS` (mandatory)
- * :setting:`FEED_EXPORT_ENCODING`
- * :setting:`FEED_STORE_EMPTY`
- * :setting:`FEED_EXPORT_FIELDS`
- * :setting:`FEED_EXPORT_INDENT`
- * :setting:`FEED_STORAGES`
- * :setting:`FEED_STORAGE_FTP_ACTIVE`
- * :setting:`FEED_STORAGE_S3_ACL`
- * :setting:`FEED_EXPORTERS`
+- :setting:`FEEDS` (mandatory)
+- :setting:`FEED_EXPORT_ENCODING`
+- :setting:`FEED_STORE_EMPTY`
+- :setting:`FEED_EXPORT_FIELDS`
+- :setting:`FEED_EXPORT_INDENT`
+- :setting:`FEED_STORAGES`
+- :setting:`FEED_STORAGE_FTP_ACTIVE`
+- :setting:`FEED_STORAGE_S3_ACL`
+- :setting:`FEED_EXPORTERS`
+- :setting:`FEED_EXPORT_BATCH_ITEM_COUNT`
.. currentmodule:: scrapy.extensions.feedexport
@@ -235,6 +339,7 @@ Default: ``{}``
A dictionary in which every key is a feed URI (or a :class:`pathlib.Path`
object) and each value is a nested dictionary containing configuration
parameters for the specific feed.
+
This setting is required for enabling the feed export feature.
See :ref:`topics-feed-storage-backends` for supported URI schemes.
@@ -246,31 +351,87 @@ For instance::
'format': 'json',
'encoding': 'utf8',
'store_empty': False,
+ 'item_classes': [MyItemClass1, 'myproject.items.MyItemClass2'],
'fields': None,
'indent': 4,
+ 'item_export_kwargs': {
+ 'export_empty_fields': True,
+ },
},
'/home/user/documents/items.xml': {
'format': 'xml',
'fields': ['name', 'price'],
+ 'item_filter': MyCustomFilter1,
'encoding': 'latin1',
'indent': 8,
},
pathlib.Path('items.csv'): {
'format': 'csv',
'fields': ['price', 'name'],
+ 'item_filter': 'myproject.filters.MyCustomFilter2',
},
}
-The following is a list of the accepted keys and the setting that is used
-as a fallback value if that key is not provided for a specific feed definition.
+.. _feed-options:
+
+The following is a list of the accepted keys and the setting that is used
+as a fallback value if that key is not provided for a specific feed definition:
+
+- ``format``: the :ref:`serialization format `.
+
+ This setting is mandatory, there is no fallback value.
+
+- ``batch_item_count``: falls back to
+ :setting:`FEED_EXPORT_BATCH_ITEM_COUNT`.
+
+ .. versionadded:: 2.3.0
+
+- ``encoding``: falls back to :setting:`FEED_EXPORT_ENCODING`.
+
+- ``fields``: falls back to :setting:`FEED_EXPORT_FIELDS`.
+
+- ``item_classes``: list of :ref:`item classes ` to export.
+
+ If undefined or empty, all items are exported.
+
+ .. versionadded:: VERSION
+
+- ``item_filter``: a :ref:`filter class ` to filter items to export.
+
+ :class:`~scrapy.extensions.feedexport.ItemFilter` is used be default.
+
+ .. versionadded:: VERSION
+
+- ``indent``: falls back to :setting:`FEED_EXPORT_INDENT`.
+
+- ``item_export_kwargs``: :class:`dict` with keyword arguments for the corresponding :ref:`item exporter class `.
+
+ .. versionadded:: 2.4.0
+
+- ``overwrite``: whether to overwrite the file if it already exists
+ (``True``) or append to its content (``False``).
+
+ The default value depends on the :ref:`storage backend
+ `:
+
+ - :ref:`topics-feed-storage-fs`: ``False``
+
+ - :ref:`topics-feed-storage-ftp`: ``True``
+
+ .. note:: Some FTP servers may not support appending to files (the
+ ``APPE`` FTP command).
+
+ - :ref:`topics-feed-storage-s3`: ``True`` (appending `is not supported
+ `_)
+
+ - :ref:`topics-feed-storage-stdout`: ``False`` (overwriting is not supported)
+
+ .. versionadded:: 2.4.0
+
+- ``store_empty``: falls back to :setting:`FEED_STORE_EMPTY`.
+
+- ``uri_params``: falls back to :setting:`FEED_URI_PARAMS`.
-* ``format``: the serialization format to be used for the feed.
- See :ref:`topics-feed-format` for possible values.
- Mandatory, no fallback setting
-* ``encoding``: falls back to :setting:`FEED_EXPORT_ENCODING`
-* ``fields``: falls back to :setting:`FEED_EXPORT_FIELDS`
-* ``indent``: falls back to :setting:`FEED_EXPORT_INDENT`
-* ``store_empty``: falls back to :setting:`FEED_STORE_EMPTY`
.. setting:: FEED_EXPORT_ENCODING
@@ -298,8 +459,8 @@ Example: ``FEED_EXPORT_FIELDS = ["foo", "bar", "baz"]``.
Use FEED_EXPORT_FIELDS option to define fields to export and their order.
-When FEED_EXPORT_FIELDS is empty or None (default), Scrapy uses fields
-defined in dicts or :class:`~.Item` subclasses a spider is yielding.
+When FEED_EXPORT_FIELDS is empty or None (default), Scrapy uses the fields
+defined in :ref:`item objects ` yielded by your spider.
If an exporter requires a fixed set of fields (this is the case for
:ref:`CSV ` export format) and FEED_EXPORT_FIELDS
@@ -425,7 +586,117 @@ format in :setting:`FEED_EXPORTERS`. E.g., to disable the built-in CSV exporter
'csv': None,
}
+
+.. setting:: FEED_EXPORT_BATCH_ITEM_COUNT
+
+FEED_EXPORT_BATCH_ITEM_COUNT
+----------------------------
+
+.. versionadded:: 2.3.0
+
+Default: ``0``
+
+If assigned an integer number higher than ``0``, Scrapy generates multiple output files
+storing up to the specified number of items in each output file.
+
+When generating multiple output files, you must use at least one of the following
+placeholders in the feed URI to indicate how the different output file names are
+generated:
+
+* ``%(batch_time)s`` - gets replaced by a timestamp when the feed is being created
+ (e.g. ``2020-03-28T14-45-08.237134``)
+
+* ``%(batch_id)d`` - gets replaced by the 1-based sequence number of the batch.
+
+ Use :ref:`printf-style string formatting ` to
+ alter the number format. For example, to make the batch ID a 5-digit
+ number by introducing leading zeroes as needed, use ``%(batch_id)05d``
+ (e.g. ``3`` becomes ``00003``, ``123`` becomes ``00123``).
+
+For instance, if your settings include::
+
+ FEED_EXPORT_BATCH_ITEM_COUNT = 100
+
+And your :command:`crawl` command line is::
+
+ scrapy crawl spidername -o "dirname/%(batch_id)d-filename%(batch_time)s.json"
+
+The command line above can generate a directory tree like::
+
+ ->projectname
+ -->dirname
+ --->1-filename2020-03-28T14-45-08.237134.json
+ --->2-filename2020-03-28T14-45-09.148903.json
+ --->3-filename2020-03-28T14-45-10.046092.json
+
+Where the first and second files contain exactly 100 items. The last one contains
+100 items or fewer.
+
+
+.. setting:: FEED_URI_PARAMS
+
+FEED_URI_PARAMS
+---------------
+
+Default: ``None``
+
+A string with the import path of a function to set the parameters to apply with
+:ref:`printf-style string formatting ` to the
+feed URI.
+
+The function signature should be as follows:
+
+.. function:: uri_params(params, spider)
+
+ Return a :class:`dict` of key-value pairs to apply to the feed URI using
+ :ref:`printf-style string formatting `.
+
+ :param params: default key-value pairs
+
+ Specifically:
+
+ - ``batch_id``: ID of the file batch. See
+ :setting:`FEED_EXPORT_BATCH_ITEM_COUNT`.
+
+ If :setting:`FEED_EXPORT_BATCH_ITEM_COUNT` is ``0``, ``batch_id``
+ is always ``1``.
+
+ .. versionadded:: 2.3.0
+
+ - ``batch_time``: UTC date and time, in ISO format with ``:``
+ replaced with ``-``.
+
+ See :setting:`FEED_EXPORT_BATCH_ITEM_COUNT`.
+
+ .. versionadded:: 2.3.0
+
+ - ``time``: ``batch_time``, with microseconds set to ``0``.
+ :type params: dict
+
+ :param spider: source spider of the feed items
+ :type spider: scrapy.Spider
+
+For example, to include the :attr:`name ` of the
+source spider in the feed URI:
+
+#. Define the following function somewhere in your project::
+
+ # myproject/utils.py
+ def uri_params(params, spider):
+ return {**params, 'spider_name': spider.name}
+
+#. Point :setting:`FEED_URI_PARAMS` to that function in your settings::
+
+ # myproject/settings.py
+ FEED_URI_PARAMS = 'myproject.utils.uri_params'
+
+#. Use ``%(spider_name)s`` in your feed URI::
+
+ scrapy crawl -o "%(spider_name)s.jl"
+
+
.. _URIs: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
.. _Amazon S3: https://aws.amazon.com/s3/
.. _botocore: https://github.com/boto/botocore
.. _Canned ACL: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+.. _Google Cloud Storage: https://cloud.google.com/storage/
diff --git a/docs/topics/item-pipeline.rst b/docs/topics/item-pipeline.rst
index c9194caa1..5351a2293 100644
--- a/docs/topics/item-pipeline.rst
+++ b/docs/topics/item-pipeline.rst
@@ -27,18 +27,22 @@ Each item pipeline component is a Python class that must implement the following
.. method:: process_item(self, item, spider)
- This method is called for every item pipeline component. :meth:`process_item`
- must either: return a dict with data, return an :class:`~scrapy.item.Item`
- (or any descendant class) object, return a
- :class:`~twisted.internet.defer.Deferred` or raise
- :exc:`~scrapy.exceptions.DropItem` exception. Dropped items are no longer
- processed by further pipeline components.
+ This method is called for every item pipeline component.
- :param item: the item scraped
- :type item: :class:`~scrapy.item.Item` object or a dict
+ `item` is an :ref:`item object `, see
+ :ref:`supporting-item-types`.
+
+ :meth:`process_item` must either: return an :ref:`item object `,
+ return a :class:`~twisted.internet.defer.Deferred` or raise a
+ :exc:`~scrapy.exceptions.DropItem` exception.
+
+ Dropped items are no longer processed by further pipeline components.
+
+ :param item: the scraped item
+ :type item: :ref:`item object `
:param spider: the spider which scraped the item
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
Additionally, they may also implement the following methods:
@@ -47,14 +51,14 @@ Additionally, they may also implement the following methods:
This method is called when the spider is opened.
:param spider: the spider which was opened
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: close_spider(self, spider)
This method is called when the spider is closed.
:param spider: the spider which was closed
- :type spider: :class:`~scrapy.spiders.Spider` object
+ :type spider: :class:`~scrapy.Spider` object
.. method:: from_crawler(cls, crawler)
@@ -79,19 +83,20 @@ Let's take a look at the following hypothetical pipeline that adjusts the
(``price_excludes_vat`` attribute), and drops those items which don't
contain a price::
+ from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
-
class PricePipeline:
vat_factor = 1.15
def process_item(self, item, spider):
- if item.get('price'):
- if item.get('price_excludes_vat'):
- item['price'] = item['price'] * self.vat_factor
+ adapter = ItemAdapter(item)
+ if adapter.get('price'):
+ if adapter.get('price_excludes_vat'):
+ adapter['price'] = adapter['price'] * self.vat_factor
return item
else:
- raise DropItem("Missing price in %s" % item)
+ raise DropItem(f"Missing price in {item}")
Write items to a JSON file
@@ -103,6 +108,8 @@ format::
import json
+ from itemadapter import ItemAdapter
+
class JsonWriterPipeline:
def open_spider(self, spider):
@@ -112,7 +119,7 @@ format::
self.file.close()
def process_item(self, item, spider):
- line = json.dumps(dict(item)) + "\n"
+ line = json.dumps(ItemAdapter(item).asdict()) + "\n"
self.file.write(line)
return item
@@ -131,6 +138,7 @@ The main point of this example is to show how to use :meth:`from_crawler`
method and how to clean up the resources properly.::
import pymongo
+ from itemadapter import ItemAdapter
class MongoPipeline:
@@ -155,7 +163,7 @@ method and how to clean up the resources properly.::
self.client.close()
def process_item(self, item, spider):
- self.db[self.collection_name].insert_one(dict(item))
+ self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
return item
.. _MongoDB: https://www.mongodb.com/
@@ -177,10 +185,11 @@ item.
::
- import scrapy
import hashlib
from urllib.parse import quote
+ import scrapy
+ from itemadapter import ItemAdapter
class ScreenshotPipeline:
"""Pipeline that uses Splash to render screenshot of
@@ -189,7 +198,8 @@ item.
SPLASH_URL = "http://localhost:8050/render.png?url={}"
async def process_item(self, item, spider):
- encoded_item_url = quote(item["url"])
+ adapter = ItemAdapter(item)
+ encoded_item_url = quote(adapter["url"])
screenshot_url = self.SPLASH_URL.format(encoded_item_url)
request = scrapy.Request(screenshot_url)
response = await spider.crawler.engine.download(request, spider)
@@ -199,14 +209,14 @@ item.
return item
# Save screenshot to file, filename will be hash of url.
- url = item["url"]
+ url = adapter["url"]
url_hash = hashlib.md5(url.encode("utf8")).hexdigest()
- filename = "{}.png".format(url_hash)
+ filename = f"{url_hash}.png"
with open(filename, "wb") as f:
f.write(response.body)
# Store filename in item.
- item["screenshot_filename"] = filename
+ adapter["screenshot_filename"] = filename
return item
.. _Splash: https://splash.readthedocs.io/en/stable/
@@ -219,6 +229,7 @@ already processed. Let's say that our items have a unique id, but our spider
returns multiples items with the same id::
+ from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class DuplicatesPipeline:
@@ -227,10 +238,11 @@ returns multiples items with the same id::
self.ids_seen = set()
def process_item(self, item, spider):
- if item['id'] in self.ids_seen:
- raise DropItem("Duplicate item found: %s" % item)
+ adapter = ItemAdapter(item)
+ if adapter['id'] in self.ids_seen:
+ raise DropItem(f"Duplicate item found: {item!r}")
else:
- self.ids_seen.add(item['id'])
+ self.ids_seen.add(adapter['id'])
return item
diff --git a/docs/topics/items.rst b/docs/topics/items.rst
index 0941a8a1b..7cd482d07 100644
--- a/docs/topics/items.rst
+++ b/docs/topics/items.rst
@@ -8,29 +8,156 @@ Items
:synopsis: Item and Field classes
The main goal in scraping is to extract structured data from unstructured
-sources, typically, web pages. Scrapy spiders can return the extracted data
-as Python dicts. While convenient and familiar, Python dicts lack structure:
-it is easy to make a typo in a field name or return inconsistent data,
-especially in a larger project with many spiders.
+sources, typically, web pages. :ref:`Spiders ` may return the
+extracted data as `items`, Python objects that define key-value pairs.
-To define common output data format Scrapy provides the :class:`Item` class.
-:class:`Item` objects are simple containers used to collect the scraped data.
-They provide an API similar to :class:`dict` API with a convenient syntax
-for declaring their available fields.
+Scrapy supports :ref:`multiple types of items `. When you create an
+item, you may use whichever type of item you want. When you write code that
+receives an item, your code should :ref:`work for any item type
+`.
-Various Scrapy components use extra information provided by Items:
-exporters look at declared fields to figure out columns to export,
-serialization can be customized using Item fields metadata, :mod:`trackref`
-tracks Item instances to help find memory leaks
-(see :ref:`topics-leaks-trackrefs`), etc.
+.. _item-types:
+
+Item Types
+==========
+
+Scrapy supports the following types of items, via the `itemadapter`_ library:
+:ref:`dictionaries `, :ref:`Item objects `,
+:ref:`dataclass objects `, and :ref:`attrs objects `.
+
+.. _itemadapter: https://github.com/scrapy/itemadapter
+
+.. _dict-items:
+
+Dictionaries
+------------
+
+As an item type, :class:`dict` is convenient and familiar.
+
+.. _item-objects:
+
+Item objects
+------------
+
+:class:`Item` provides a :class:`dict`-like API plus additional features that
+make it the most feature-complete item type:
+
+.. class:: scrapy.item.Item([arg])
+.. class:: scrapy.Item([arg])
+
+ :class:`Item` objects replicate the standard :class:`dict` API, including
+ its ``__init__`` method.
+
+ :class:`Item` allows defining field names, so that:
+
+ - :class:`KeyError` is raised when using undefined field names (i.e.
+ prevents typos going unnoticed)
+
+ - :ref:`Item exporters ` can export all fields by
+ default even if the first scraped object does not have values for all
+ of them
+
+ :class:`Item` also allows defining field metadata, which can be used to
+ :ref:`customize serialization `.
+
+ :mod:`trackref` tracks :class:`Item` objects to help find memory leaks
+ (see :ref:`topics-leaks-trackrefs`).
+
+ :class:`Item` objects also provide the following additional API members:
+
+ .. automethod:: copy
+
+ .. automethod:: deepcopy
+
+ .. attribute:: fields
+
+ A dictionary containing *all declared fields* for this Item, not only
+ those populated. The keys are the field names and the values are the
+ :class:`Field` objects used in the :ref:`Item declaration
+ `.
+
+Example::
+
+ from scrapy.item import Item, Field
+
+ class CustomItem(Item):
+ one_field = Field()
+ another_field = Field()
+
+.. _dataclass-items:
+
+Dataclass objects
+-----------------
+
+.. versionadded:: 2.2
+
+:func:`~dataclasses.dataclass` allows defining item classes with field names,
+so that :ref:`item exporters ` can export all fields by
+default even if the first scraped object does not have values for all of them.
+
+Additionally, ``dataclass`` items also allow to:
+
+* define the type and default value of each defined field.
+
+* define custom field metadata through :func:`dataclasses.field`, which can be used to
+ :ref:`customize serialization `.
+
+They work natively in Python 3.7 or later, or using the `dataclasses
+backport`_ in Python 3.6.
+
+.. _dataclasses backport: https://pypi.org/project/dataclasses/
+
+Example::
+
+ from dataclasses import dataclass
+
+ @dataclass
+ class CustomItem:
+ one_field: str
+ another_field: int
+
+.. note:: Field types are not enforced at run time.
+
+.. _attrs-items:
+
+attr.s objects
+--------------
+
+.. versionadded:: 2.2
+
+:func:`attr.s` allows defining item classes with field names,
+so that :ref:`item exporters ` can export all fields by
+default even if the first scraped object does not have values for all of them.
+
+Additionally, ``attr.s`` items also allow to:
+
+* define the type and default value of each defined field.
+
+* define custom field :ref:`metadata `, which can be used to
+ :ref:`customize serialization `.
+
+In order to use this type, the :doc:`attrs package ` needs to be installed.
+
+Example::
+
+ import attr
+
+ @attr.s
+ class CustomItem:
+ one_field = attr.ib()
+ another_field = attr.ib()
+
+
+Working with Item objects
+=========================
.. _topics-items-declaring:
-Declaring Items
-===============
+Declaring Item subclasses
+-------------------------
-Items are declared using a simple class definition syntax and :class:`Field`
-objects. Here is an example::
+Item subclasses are declared using a simple class definition syntax and
+:class:`Field` objects. Here is an example::
import scrapy
@@ -48,10 +175,11 @@ objects. Here is an example::
.. _Django: https://www.djangoproject.com/
.. _Django Models: https://docs.djangoproject.com/en/dev/topics/db/models/
+
.. _topics-items-fields:
-Item Fields
-===========
+Declaring fields
+----------------
:class:`Field` objects are used to specify metadata for each field. For
example, the serializer function for the ``last_updated`` field illustrated in
@@ -72,15 +200,32 @@ It's important to note that the :class:`Field` objects used to declare the item
do not stay assigned as class attributes. Instead, they can be accessed through
the :attr:`Item.fields` attribute.
-Working with Items
-==================
+.. class:: scrapy.item.Field([arg])
+.. class:: scrapy.Field([arg])
+
+ The :class:`Field` class is just an alias to the built-in :class:`dict` class and
+ doesn't provide any extra functionality or attributes. In other words,
+ :class:`Field` objects are plain-old Python dicts. A separate class is used
+ to support the :ref:`item declaration syntax `
+ based on class attributes.
+
+.. note:: Field metadata can also be declared for ``dataclass`` and ``attrs``
+ items. Please refer to the documentation for `dataclasses.field`_ and
+ `attr.ib`_ for additional information.
+
+ .. _dataclasses.field: https://docs.python.org/3/library/dataclasses.html#dataclasses.field
+ .. _attr.ib: https://www.attrs.org/en/stable/api.html#attr.ib
+
+
+Working with Item objects
+-------------------------
Here are some examples of common tasks performed with items, using the
``Product`` item :ref:`declared above `. You will
notice the API is very similar to the :class:`dict` API.
Creating items
---------------
+''''''''''''''
>>> product = Product(name='Desktop PC', price=1000)
>>> print(product)
@@ -88,7 +233,7 @@ Product(name='Desktop PC', price=1000)
Getting field values
---------------------
+''''''''''''''''''''
>>> product['name']
Desktop PC
@@ -128,7 +273,7 @@ False
Setting field values
---------------------
+''''''''''''''''''''
>>> product['last_updated'] = 'today'
>>> product['last_updated']
@@ -141,7 +286,7 @@ KeyError: 'Product does not support field: lala'
Accessing all populated values
-------------------------------
+''''''''''''''''''''''''''''''
To access all populated values, just use the typical :class:`dict` API:
@@ -155,7 +300,7 @@ To access all populated values, just use the typical :class:`dict` API:
.. _copying-items:
Copying items
--------------
+'''''''''''''
To copy an item, you must first decide whether you want a shallow copy or a
deep copy.
@@ -174,16 +319,16 @@ If that is not the desired behavior, use a deep copy instead.
See :mod:`copy` for more information.
To create a shallow copy of an item, you can either call
-:meth:`~scrapy.item.Item.copy` on an existing item
+:meth:`~scrapy.Item.copy` on an existing item
(``product2 = product.copy()``) or instantiate your item class from an existing
item (``product2 = Product(product)``).
-To create a deep copy, call :meth:`~scrapy.item.Item.deepcopy` instead
+To create a deep copy, call :meth:`~scrapy.Item.deepcopy` instead
(``product2 = product.deepcopy()``).
Other common tasks
-------------------
+''''''''''''''''''
Creating dicts from items:
@@ -201,8 +346,8 @@ Traceback (most recent call last):
KeyError: 'Product does not support field: lala'
-Extending Items
-===============
+Extending Item subclasses
+-------------------------
You can extend Items (to add more fields or to change some metadata for some
fields) by declaring a subclass of your original Item.
@@ -222,39 +367,25 @@ appending more values, or changing existing values, like this::
That adds (or replaces) the ``serializer`` metadata key for the ``name`` field,
keeping all the previously existing metadata values.
-Item objects
-============
-.. class:: Item([arg])
+.. _supporting-item-types:
- Return a new Item optionally initialized from the given argument.
+Supporting All Item Types
+=========================
- Items replicate the standard :class:`dict` API, including its ``__init__``
- method, and also provide the following additional API members:
+In code that receives an item, such as methods of :ref:`item pipelines
+` or :ref:`spider middlewares
+`, it is a good practice to use the
+:class:`~itemadapter.ItemAdapter` class and the
+:func:`~itemadapter.is_item` function to write code that works for
+any :ref:`supported item type `:
- .. automethod:: copy
+.. autoclass:: itemadapter.ItemAdapter
- .. automethod:: deepcopy
+.. autofunction:: itemadapter.is_item
- .. attribute:: fields
- A dictionary containing *all declared fields* for this Item, not only
- those populated. The keys are the field names and the values are the
- :class:`Field` objects used in the :ref:`Item declaration
- `.
-
-Field objects
-=============
-
-.. class:: Field([arg])
-
- The :class:`Field` class is just an alias to the built-in :class:`dict` class and
- doesn't provide any extra functionality or attributes. In other words,
- :class:`Field` objects are plain-old Python dicts. A separate class is used
- to support the :ref:`item declaration syntax `
- based on class attributes.
-
-Other classes related to Item
-=============================
+Other classes related to items
+==============================
.. autoclass:: ItemMeta
diff --git a/docs/topics/jobs.rst b/docs/topics/jobs.rst
index 58601824a..e49f37a2f 100644
--- a/docs/topics/jobs.rst
+++ b/docs/topics/jobs.rst
@@ -65,7 +65,7 @@ Cookies expiration
------------------
Cookies may expire. So, if you don't resume your spider quickly the requests
-scheduled may no longer work. This won't be an issue if you spider doesn't rely
+scheduled may no longer work. This won't be an issue if your spider doesn't rely
on cookies.
@@ -74,10 +74,10 @@ on cookies.
Request serialization
---------------------
-For persistence to work, :class:`~scrapy.http.Request` objects must be
+For persistence to work, :class:`~scrapy.Request` objects must be
serializable with :mod:`pickle`, except for the ``callback`` and ``errback``
values passed to their ``__init__`` method, which must be methods of the
-running :class:`~scrapy.spiders.Spider` class.
+running :class:`~scrapy.Spider` class.
If you wish to log the requests that couldn't be serialized, you can set the
:setting:`SCHEDULER_DEBUG` setting to ``True`` in the project's settings page.
diff --git a/docs/topics/leaks.rst b/docs/topics/leaks.rst
index ceb708c7e..477652704 100644
--- a/docs/topics/leaks.rst
+++ b/docs/topics/leaks.rst
@@ -4,7 +4,7 @@
Debugging memory leaks
======================
-In Scrapy, objects such as Requests, Responses and Items have a finite
+In Scrapy, objects such as requests, responses and items have a finite
lifetime: they are created, used for a while, and finally destroyed.
From all those objects, the Request is probably the one with the longest
@@ -27,7 +27,7 @@ Common causes of memory leaks
It happens quite often (sometimes by accident, sometimes on purpose) that the
Scrapy developer passes objects referenced in Requests (for example, using the
-:attr:`~scrapy.http.Request.cb_kwargs` or :attr:`~scrapy.http.Request.meta`
+:attr:`~scrapy.Request.cb_kwargs` or :attr:`~scrapy.Request.meta`
attributes or the request callback function) and that effectively bounds the
lifetime of those referenced objects to the lifetime of the Request. This is,
by far, the most common cause of memory leaks in Scrapy projects, and a quite
@@ -48,9 +48,9 @@ Too Many Requests?
------------------
By default Scrapy keeps the request queue in memory; it includes
-:class:`~scrapy.http.Request` objects and all objects
-referenced in Request attributes (e.g. in :attr:`~scrapy.http.Request.cb_kwargs`
-and :attr:`~scrapy.http.Request.meta`).
+:class:`~scrapy.Request` objects and all objects
+referenced in Request attributes (e.g. in :attr:`~scrapy.Request.cb_kwargs`
+and :attr:`~scrapy.Request.meta`).
While not necessarily a leak, this can take a lot of memory. Enabling
:ref:`persistent job queue ` could help keeping memory usage
in control.
@@ -61,8 +61,8 @@ Debugging memory leaks with ``trackref``
========================================
:mod:`trackref` is a module provided by Scrapy to debug the most common cases of
-memory leaks. It basically tracks the references to all live Requests,
-Responses, Item and Selector objects.
+memory leaks. It basically tracks the references to all live Request,
+Response, Item, Spider and Selector objects.
You can enter the telnet console and inspect how many objects (of the classes
mentioned above) are currently alive using the ``prefs()`` function which is an
@@ -90,11 +90,11 @@ Which objects are tracked?
The objects tracked by ``trackrefs`` are all from these classes (and all its
subclasses):
-* :class:`scrapy.http.Request`
+* :class:`scrapy.Request`
* :class:`scrapy.http.Response`
-* :class:`scrapy.item.Item`
-* :class:`scrapy.selector.Selector`
-* :class:`scrapy.spiders.Spider`
+* :class:`scrapy.Item`
+* :class:`scrapy.Selector`
+* :class:`scrapy.Spider`
A real example
--------------
@@ -102,7 +102,7 @@ A real example
Let's see a concrete example of a hypothetical case of memory leaks.
Suppose we have some spider with a line similar to this one::
- return Request("http://www.somenastyspider.com/product.php?pid=%d" % product_id,
+ return Request(f"http://www.somenastyspider.com/product.php?pid={product_id}",
callback=self.parse, cb_kwargs={'referer': response})
That line is passing a response reference inside a request which effectively
@@ -179,7 +179,7 @@ Here are the functions available in the :mod:`~scrapy.utils.trackref` module.
:param ignore: if given, all objects from the specified class (or tuple of
classes) will be ignored.
- :type ignore: class or classes tuple
+ :type ignore: type or tuple
.. function:: get_oldest(class_name)
@@ -200,11 +200,10 @@ Debugging memory leaks with muppy
``trackref`` provides a very convenient mechanism for tracking down memory
leaks, but it only keeps track of the objects that are more likely to cause
-memory leaks (Requests, Responses, Items, and Selectors). However, there are
-other cases where the memory leaks could come from other (more or less obscure)
-objects. If this is your case, and you can't find your leaks using ``trackref``,
-you still have another resource: the muppy library.
-
+memory leaks. However, there are other cases where the memory leaks could come
+from other (more or less obscure) objects. If this is your case, and you can't
+find your leaks using ``trackref``, you still have another resource: the muppy
+library.
You can use muppy from `Pympler`_.
diff --git a/docs/topics/link-extractors.rst b/docs/topics/link-extractors.rst
index 0162a331a..e12ad45e0 100644
--- a/docs/topics/link-extractors.rst
+++ b/docs/topics/link-extractors.rst
@@ -10,12 +10,19 @@ The ``__init__`` method of
:class:`~scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor` takes settings that
determine which links may be extracted. :class:`LxmlLinkExtractor.extract_links
` returns a
-list of matching :class:`scrapy.link.Link` objects from a
+list of matching :class:`~scrapy.link.Link` objects from a
:class:`~scrapy.http.Response` object.
Link extractors are used in :class:`~scrapy.spiders.CrawlSpider` spiders
-through a set of :class:`~scrapy.spiders.Rule` objects. You can also use link
-extractors in regular spiders.
+through a set of :class:`~scrapy.spiders.Rule` objects.
+
+You can also use link extractors in regular spiders. For example, you can instantiate
+:class:`LinkExtractor ` into a class
+variable in your spider, and use it from your spider callbacks::
+
+ def parse(self, response):
+ for link in self.link_extractor.extract_links(response):
+ yield Request(link.url, callback=self.parse)
.. _topics-link-extractors-ref:
@@ -46,13 +53,13 @@ LxmlLinkExtractor
:param allow: a single regular expression (or list of regular expressions)
that the (absolute) urls must match in order to be extracted. If not
given (or empty), it will match all links.
- :type allow: a regular expression (or list of)
+ :type allow: str or list
:param deny: a single regular expression (or list of regular expressions)
that the (absolute) urls must match in order to be excluded (i.e. not
extracted). It has precedence over the ``allow`` parameter. If not
given (or empty) it won't exclude any links.
- :type deny: a regular expression (or list of)
+ :type deny: str or list
:param allow_domains: a single value or a list of string containing
domains which will be considered for extracting the links
@@ -88,7 +95,7 @@ LxmlLinkExtractor
that the link's text must match in order to be extracted. If not
given (or empty), it will match all links. If a list of regular expressions is
given, the link will be extracted if it matches at least one.
- :type restrict_text: a regular expression (or list of)
+ :type restrict_text: str or list
:param tags: a tag or a list of tags to consider when extracting links.
Defaults to ``('a', 'area')``.
@@ -106,11 +113,11 @@ LxmlLinkExtractor
different for requests with canonicalized and raw URLs. If you're
using LinkExtractor to follow links it is more robust to
keep the default ``canonicalize=False``.
- :type canonicalize: boolean
+ :type canonicalize: bool
:param unique: whether duplicate filtering should be applied to extracted
links.
- :type unique: boolean
+ :type unique: bool
:param process_value: a function which receives each value extracted from
the tag and attributes scanned and can modify the value and return a
@@ -132,7 +139,7 @@ LxmlLinkExtractor
if m:
return m.group(1)
- :type process_value: callable
+ :type process_value: collections.abc.Callable
:param strip: whether to strip whitespaces from extracted attributes.
According to HTML5 standard, leading and trailing whitespaces
@@ -141,8 +148,16 @@ LxmlLinkExtractor
elements, etc., so LinkExtractor strips space chars by default.
Set ``strip=False`` to turn it off (e.g. if you're extracting urls
from elements or attributes which allow leading/trailing whitespaces).
- :type strip: boolean
+ :type strip: bool
.. automethod:: extract_links
+Link
+----
+
+.. module:: scrapy.link
+ :synopsis: Link from link extractors
+
+.. autoclass:: Link
+
.. _scrapy.linkextractors: https://github.com/scrapy/scrapy/blob/master/scrapy/linkextractors/__init__.py
diff --git a/docs/topics/loaders.rst b/docs/topics/loaders.rst
index eb804f1db..c0f534493 100644
--- a/docs/topics/loaders.rst
+++ b/docs/topics/loaders.rst
@@ -7,13 +7,12 @@ Item Loaders
.. module:: scrapy.loader
:synopsis: Item Loader class
-Item Loaders provide a convenient mechanism for populating scraped :ref:`Items
-`. Even though Items can be populated using their own
-dictionary-like API, Item Loaders provide a much more convenient API for
-populating them from a scraping process, by automating some common tasks like
-parsing the raw extracted data before assigning it.
+Item Loaders provide a convenient mechanism for populating scraped :ref:`items
+`. Even though items can be populated directly, Item Loaders provide a
+much more convenient API for populating them from a scraping process, by automating
+some common tasks like parsing the raw extracted data before assigning it.
-In other words, :ref:`Items ` provide the *container* of
+In other words, :ref:`items ` provide the *container* of
scraped data, while Item Loaders provide the mechanism for *populating* that
container.
@@ -21,14 +20,18 @@ Item Loaders are designed to provide a flexible, efficient and easy mechanism
for extending and overriding different field parsing rules, either by spider,
or by source format (HTML, XML, etc) without becoming a nightmare to maintain.
+.. note:: Item Loaders are an extension of the itemloaders_ library that make it
+ easier to work with Scrapy by adding support for
+ :ref:`responses `.
+
Using Item Loaders to populate items
====================================
To use an Item Loader, you must first instantiate it. You can either
-instantiate it with a dict-like object (e.g. Item or dict) or without one, in
-which case an Item is automatically instantiated in the Item Loader ``__init__`` method
-using the Item class specified in the :attr:`ItemLoader.default_item_class`
-attribute.
+instantiate it with an :ref:`item object ` or without one, in which
+case an :ref:`item object ` is automatically created in the
+Item Loader ``__init__`` method using the :ref:`item ` class
+specified in the :attr:`ItemLoader.default_item_class` attribute.
Then, you start collecting values into the Item Loader, typically using
:ref:`Selectors `. You can add more than one value to
@@ -77,6 +80,31 @@ called which actually returns the item populated with the data
previously extracted and collected with the :meth:`~ItemLoader.add_xpath`,
:meth:`~ItemLoader.add_css`, and :meth:`~ItemLoader.add_value` calls.
+
+.. _topics-loaders-dataclass:
+
+Working with dataclass items
+============================
+
+By default, :ref:`dataclass items ` require all fields to be
+passed when created. This could be an issue when using dataclass items with
+item loaders: unless a pre-populated item is passed to the loader, fields
+will be populated incrementally using the loader's :meth:`~ItemLoader.add_xpath`,
+:meth:`~ItemLoader.add_css` and :meth:`~ItemLoader.add_value` methods.
+
+One approach to overcome this is to define items using the
+:func:`~dataclasses.field` function, with a ``default`` argument::
+
+ from dataclasses import dataclass, field
+ from typing import Optional
+
+ @dataclass
+ class InventoryItem:
+ name: Optional[str] = field(default=None)
+ price: Optional[float] = field(default=None)
+ stock: Optional[int] = field(default=None)
+
+
.. _topics-loaders-processors:
Input and Output processors
@@ -88,7 +116,7 @@ received (through the :meth:`~ItemLoader.add_xpath`, :meth:`~ItemLoader.add_css`
:meth:`~ItemLoader.add_value` methods) and the result of the input processor is
collected and kept inside the ItemLoader. After collecting all data, the
:meth:`ItemLoader.load_item` method is called to populate and get the populated
-:class:`~scrapy.item.Item` object. That's when the output processor is
+:ref:`item object `. That's when the output processor is
called with the data previously collected (and processed using the input
processor). The result of the output processor is the final value that gets
assigned to the item.
@@ -149,28 +177,26 @@ The other thing you need to keep in mind is that the values returned by input
processors are collected internally (in lists) and then passed to output
processors to populate the fields.
-Last, but not least, Scrapy comes with some :ref:`commonly used processors
-` built-in for convenience.
-
+Last, but not least, itemloaders_ comes with some :ref:`commonly used
+processors ` built-in for convenience.
Declaring Item Loaders
======================
-Item Loaders are declared like Items, by using a class definition syntax. Here
-is an example::
+Item Loaders are declared using a class definition syntax. Here is an example::
+ from itemloaders.processors import TakeFirst, MapCompose, Join
from scrapy.loader import ItemLoader
- from scrapy.loader.processors import TakeFirst, MapCompose, Join
class ProductLoader(ItemLoader):
default_output_processor = TakeFirst()
- name_in = MapCompose(unicode.title)
+ name_in = MapCompose(str.title)
name_out = Join()
- price_in = MapCompose(unicode.strip)
+ price_in = MapCompose(str.strip)
# ...
@@ -192,7 +218,7 @@ output processors to use: in the :ref:`Item Field `
metadata. Here is an example::
import scrapy
- from scrapy.loader.processors import Join, MapCompose, TakeFirst
+ from itemloaders.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags
def filter_price(value):
@@ -211,10 +237,10 @@ metadata. Here is an example::
>>> from scrapy.loader import ItemLoader
>>> il = ItemLoader(item=Product())
->>> il.add_value('name', [u'Welcome to my', u'website'])
->>> il.add_value('price', [u'€', u'1000'])
+>>> il.add_value('name', ['Welcome to my', 'website'])
+>>> il.add_value('price', ['€', '1000'])
>>> il.load_item()
-{'name': u'Welcome to my website', 'price': u'1000'}
+{'name': 'Welcome to my website', 'price': '1000'}
The precedence order, for both input and output processors, is as follows:
@@ -273,248 +299,9 @@ There are several ways to modify Item Loader context values:
ItemLoader objects
==================
-.. class:: ItemLoader([item, selector, response], **kwargs)
-
- Return a new Item Loader for populating the given Item. If no item is
- given, one is instantiated automatically using the class in
- :attr:`default_item_class`.
-
- When instantiated with a ``selector`` or a ``response`` parameters
- the :class:`ItemLoader` class provides convenient mechanisms for extracting
- data from web pages using :ref:`selectors `.
-
- :param item: The item instance to populate using subsequent calls to
- :meth:`~ItemLoader.add_xpath`, :meth:`~ItemLoader.add_css`,
- or :meth:`~ItemLoader.add_value`.
- :type item: :class:`~scrapy.item.Item` object
-
- :param selector: The selector to extract data from, when using the
- :meth:`add_xpath` (resp. :meth:`add_css`) or :meth:`replace_xpath`
- (resp. :meth:`replace_css`) method.
- :type selector: :class:`~scrapy.selector.Selector` object
-
- :param response: The response used to construct the selector using the
- :attr:`default_selector_class`, unless the selector argument is given,
- in which case this argument is ignored.
- :type response: :class:`~scrapy.http.Response` object
-
- The item, selector, response and the remaining keyword arguments are
- assigned to the Loader context (accessible through the :attr:`context` attribute).
-
- :class:`ItemLoader` instances have the following methods:
-
- .. method:: get_value(value, *processors, **kwargs)
-
- Process the given ``value`` by the given ``processors`` and keyword
- arguments.
-
- Available keyword arguments:
-
- :param re: a regular expression to use for extracting data from the
- given value using :meth:`~scrapy.utils.misc.extract_regex` method,
- applied before processors
- :type re: str or compiled regex
-
- Examples:
-
- >>> from scrapy.loader.processors import TakeFirst
- >>> loader.get_value(u'name: foo', TakeFirst(), unicode.upper, re='name: (.+)')
- 'FOO`
-
- .. method:: add_value(field_name, value, *processors, **kwargs)
-
- Process and then add the given ``value`` for the given field.
-
- The value is first passed through :meth:`get_value` by giving the
- ``processors`` and ``kwargs``, and then passed through the
- :ref:`field input processor ` and its result
- appended to the data collected for that field. If the field already
- contains collected data, the new data is added.
-
- The given ``field_name`` can be ``None``, in which case values for
- multiple fields may be added. And the processed value should be a dict
- with field_name mapped to values.
-
- Examples::
-
- loader.add_value('name', u'Color TV')
- loader.add_value('colours', [u'white', u'blue'])
- loader.add_value('length', u'100')
- loader.add_value('name', u'name: foo', TakeFirst(), re='name: (.+)')
- loader.add_value(None, {'name': u'foo', 'sex': u'male'})
-
- .. method:: replace_value(field_name, value, *processors, **kwargs)
-
- Similar to :meth:`add_value` but replaces the collected data with the
- new value instead of adding it.
- .. method:: get_xpath(xpath, *processors, **kwargs)
-
- Similar to :meth:`ItemLoader.get_value` but receives an XPath instead of a
- value, which is used to extract a list of unicode strings from the
- selector associated with this :class:`ItemLoader`.
-
- :param xpath: the XPath to extract data from
- :type xpath: str
-
- :param re: a regular expression to use for extracting data from the
- selected XPath region
- :type re: str or compiled regex
-
- Examples::
-
- # HTML snippet: Color TV
- loader.get_xpath('//p[@class="product-name"]')
- # HTML snippet: the price is $1200
- loader.get_xpath('//p[@id="price"]', TakeFirst(), re='the price is (.*)')
-
- .. method:: add_xpath(field_name, xpath, *processors, **kwargs)
-
- Similar to :meth:`ItemLoader.add_value` but receives an XPath instead of a
- value, which is used to extract a list of unicode strings from the
- selector associated with this :class:`ItemLoader`.
-
- See :meth:`get_xpath` for ``kwargs``.
-
- :param xpath: the XPath to extract data from
- :type xpath: str
-
- Examples::
-
- # HTML snippet: Color TV
- loader.add_xpath('name', '//p[@class="product-name"]')
- # HTML snippet: the price is $1200
- loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
-
- .. method:: replace_xpath(field_name, xpath, *processors, **kwargs)
-
- Similar to :meth:`add_xpath` but replaces collected data instead of
- adding it.
-
- .. method:: get_css(css, *processors, **kwargs)
-
- Similar to :meth:`ItemLoader.get_value` but receives a CSS selector
- instead of a value, which is used to extract a list of unicode strings
- from the selector associated with this :class:`ItemLoader`.
-
- :param css: the CSS selector to extract data from
- :type css: str
-
- :param re: a regular expression to use for extracting data from the
- selected CSS region
- :type re: str or compiled regex
-
- Examples::
-
- # HTML snippet: Color TV
- loader.get_css('p.product-name')
- # HTML snippet: the price is $1200
- loader.get_css('p#price', TakeFirst(), re='the price is (.*)')
-
- .. method:: add_css(field_name, css, *processors, **kwargs)
-
- Similar to :meth:`ItemLoader.add_value` but receives a CSS selector
- instead of a value, which is used to extract a list of unicode strings
- from the selector associated with this :class:`ItemLoader`.
-
- See :meth:`get_css` for ``kwargs``.
-
- :param css: the CSS selector to extract data from
- :type css: str
-
- Examples::
-
- # HTML snippet: Color TV
- loader.add_css('name', 'p.product-name')
- # HTML snippet: the price is $1200
- loader.add_css('price', 'p#price', re='the price is (.*)')
-
- .. method:: replace_css(field_name, css, *processors, **kwargs)
-
- Similar to :meth:`add_css` but replaces collected data instead of
- adding it.
-
- .. method:: load_item()
-
- Populate the item with the data collected so far, and return it. The
- data collected is first passed through the :ref:`output processors
- ` to get the final value to assign to each
- item field.
-
- .. method:: nested_xpath(xpath)
-
- Create a nested loader with an xpath selector.
- The supplied selector is applied relative to selector associated
- with this :class:`ItemLoader`. The nested loader shares the :class:`Item`
- with the parent :class:`ItemLoader` so calls to :meth:`add_xpath`,
- :meth:`add_value`, :meth:`replace_value`, etc. will behave as expected.
-
- .. method:: nested_css(css)
-
- Create a nested loader with a css selector.
- The supplied selector is applied relative to selector associated
- with this :class:`ItemLoader`. The nested loader shares the :class:`Item`
- with the parent :class:`ItemLoader` so calls to :meth:`add_xpath`,
- :meth:`add_value`, :meth:`replace_value`, etc. will behave as expected.
-
- .. method:: get_collected_values(field_name)
-
- Return the collected values for the given field.
-
- .. method:: get_output_value(field_name)
-
- Return the collected values parsed using the output processor, for the
- given field. This method doesn't populate or modify the item at all.
-
- .. method:: get_input_processor(field_name)
-
- Return the input processor for the given field.
-
- .. method:: get_output_processor(field_name)
-
- Return the output processor for the given field.
-
- :class:`ItemLoader` instances have the following attributes:
-
- .. attribute:: item
-
- The :class:`~scrapy.item.Item` object being parsed by this Item Loader.
- This is mostly used as a property so when attempting to override this
- value, you may want to check out :attr:`default_item_class` first.
-
- .. attribute:: context
-
- The currently active :ref:`Context ` of this
- Item Loader.
-
- .. attribute:: default_item_class
-
- An Item class (or factory), used to instantiate items when not given in
- the ``__init__`` method.
-
- .. attribute:: default_input_processor
-
- The default input processor to use for those fields which don't specify
- one.
-
- .. attribute:: default_output_processor
-
- The default output processor to use for those fields which don't specify
- one.
-
- .. attribute:: default_selector_class
-
- The class used to construct the :attr:`selector` of this
- :class:`ItemLoader`, if only a response is given in the ``__init__`` method.
- If a selector is given in the ``__init__`` method this attribute is ignored.
- This attribute is sometimes overridden in subclasses.
-
- .. attribute:: selector
-
- The :class:`~scrapy.selector.Selector` object to extract data from.
- It's either the selector given in the ``__init__`` method or one created from
- the response given in the ``__init__`` method using the
- :attr:`default_selector_class`. This attribute is meant to be
- read-only.
+.. autoclass:: scrapy.loader.ItemLoader
+ :members:
+ :inherited-members:
.. _topics-loaders-nested:
@@ -585,7 +372,7 @@ those dashes in the final product names.
Here's how you can remove those dashes by reusing and extending the default
Product Item Loader (``ProductLoader``)::
- from scrapy.loader.processors import MapCompose
+ from itemloaders.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
def strip_dashes(x):
@@ -598,7 +385,7 @@ Another case where extending Item Loaders can be very helpful is when you have
multiple source formats, for example XML and HTML. In the XML version you may
want to remove ``CDATA`` occurrences. Here's an example of how to do it::
- from scrapy.loader.processors import MapCompose
+ from itemloaders.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata
@@ -618,156 +405,5 @@ projects. Scrapy only provides the mechanism; it doesn't impose any specific
organization of your Loaders collection - that's up to you and your project's
needs.
-.. _topics-loaders-available-processors:
-
-Available built-in processors
-=============================
-
-.. module:: scrapy.loader.processors
- :synopsis: A collection of processors to use with Item Loaders
-
-Even though you can use any callable function as input and output processors,
-Scrapy provides some commonly used processors, which are described below. Some
-of them, like the :class:`MapCompose` (which is typically used as input
-processor) compose the output of several functions executed in order, to
-produce the final parsed value.
-
-Here is a list of all built-in processors:
-
-.. class:: Identity
-
- The simplest processor, which doesn't do anything. It returns the original
- values unchanged. It doesn't receive any ``__init__`` method arguments, nor does it
- accept Loader contexts.
-
- Example:
-
- >>> from scrapy.loader.processors import Identity
- >>> proc = Identity()
- >>> proc(['one', 'two', 'three'])
- ['one', 'two', 'three']
-
-.. class:: TakeFirst
-
- Returns the first non-null/non-empty value from the values received,
- so it's typically used as an output processor to single-valued fields.
- It doesn't receive any ``__init__`` method arguments, nor does it accept Loader contexts.
-
- Example:
-
- >>> from scrapy.loader.processors import TakeFirst
- >>> proc = TakeFirst()
- >>> proc(['', 'one', 'two', 'three'])
- 'one'
-
-.. class:: Join(separator=u' ')
-
- Returns the values joined with the separator given in the ``__init__`` method, which
- defaults to ``u' '``. It doesn't accept Loader contexts.
-
- When using the default separator, this processor is equivalent to the
- function: ``u' '.join``
-
- Examples:
-
- >>> from scrapy.loader.processors import Join
- >>> proc = Join()
- >>> proc(['one', 'two', 'three'])
- 'one two three'
- >>> proc = Join('
')
- >>> proc(['one', 'two', 'three'])
- 'one
two
three'
-
-.. class:: Compose(*functions, **default_loader_context)
-
- A processor which is constructed from the composition of the given
- functions. This means that each input value of this processor is passed to
- the first function, and the result of that function is passed to the second
- function, and so on, until the last function returns the output value of
- this processor.
-
- By default, stop process on ``None`` value. This behaviour can be changed by
- passing keyword argument ``stop_on_none=False``.
-
- Example:
-
- >>> from scrapy.loader.processors import Compose
- >>> proc = Compose(lambda v: v[0], str.upper)
- >>> proc(['hello', 'world'])
- 'HELLO'
-
- Each function can optionally receive a ``loader_context`` parameter. For
- those which do, this processor will pass the currently active :ref:`Loader
- context ` through that parameter.
-
- The keyword arguments passed in the ``__init__`` method are used as the default
- Loader context values passed to each function call. However, the final
- Loader context values passed to functions are overridden with the currently
- active Loader context accessible through the :meth:`ItemLoader.context`
- attribute.
-
-.. class:: MapCompose(*functions, **default_loader_context)
-
- A processor which is constructed from the composition of the given
- functions, similar to the :class:`Compose` processor. The difference with
- this processor is the way internal results are passed among functions,
- which is as follows:
-
- The input value of this processor is *iterated* and the first function is
- applied to each element. The results of these function calls (one for each element)
- are concatenated to construct a new iterable, which is then used to apply the
- second function, and so on, until the last function is applied to each
- value of the list of values collected so far. The output values of the last
- function are concatenated together to produce the output of this processor.
-
- Each particular function can return a value or a list of values, which is
- flattened with the list of values returned by the same function applied to
- the other input values. The functions can also return ``None`` in which
- case the output of that function is ignored for further processing over the
- chain.
-
- This processor provides a convenient way to compose functions that only
- work with single values (instead of iterables). For this reason the
- :class:`MapCompose` processor is typically used as input processor, since
- data is often extracted using the
- :meth:`~scrapy.selector.Selector.extract` method of :ref:`selectors
- `, which returns a list of unicode strings.
-
- The example below should clarify how it works:
-
- >>> def filter_world(x):
- ... return None if x == 'world' else x
- ...
- >>> from scrapy.loader.processors import MapCompose
- >>> proc = MapCompose(filter_world, str.upper)
- >>> proc(['hello', 'world', 'this', 'is', 'scrapy'])
- ['HELLO, 'THIS', 'IS', 'SCRAPY']
-
- As with the Compose processor, functions can receive Loader contexts, and
- ``__init__`` method keyword arguments are used as default context values. See
- :class:`Compose` processor for more info.
-
-.. class:: SelectJmes(json_path)
-
- Queries the value using the json path provided to the ``__init__`` method and returns the output.
- Requires jmespath (https://github.com/jmespath/jmespath.py) to run.
- This processor takes only one input at a time.
-
- Example:
-
- >>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose
- >>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
- >>> proc({'foo': 'bar'})
- 'bar'
- >>> proc({'foo': {'bar': 'baz'}})
- {'bar': 'baz'}
-
- Working with Json:
-
- >>> import json
- >>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
- >>> proc_single_json_str('{"foo": "bar"}')
- 'bar'
- >>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
- >>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
- ['bar']
+.. _itemloaders: https://itemloaders.readthedocs.io/en/latest/
+.. _processors: https://itemloaders.readthedocs.io/en/latest/built-in-processors.html
diff --git a/docs/topics/logging.rst b/docs/topics/logging.rst
index 55065a1a3..dda04dc4d 100644
--- a/docs/topics/logging.rst
+++ b/docs/topics/logging.rst
@@ -93,7 +93,7 @@ path::
Logging from Spiders
====================
-Scrapy provides a :data:`~scrapy.spiders.Spider.logger` within each Spider
+Scrapy provides a :data:`~scrapy.Spider.logger` within each Spider
instance, which can be accessed and used like this::
import scrapy
@@ -101,7 +101,7 @@ instance, which can be accessed and used like this::
class MySpider(scrapy.Spider):
name = 'myspider'
- start_urls = ['https://scrapinghub.com']
+ start_urls = ['https://scrapy.org']
def parse(self, response):
self.logger.info('Parse function called on %s', response.url)
@@ -117,7 +117,7 @@ Python logger you want. For example::
class MySpider(scrapy.Spider):
name = 'myspider'
- start_urls = ['https://scrapinghub.com']
+ start_urls = ['https://scrapy.org']
def parse(self, response):
logger.info('Parse function called on %s', response.url)
@@ -242,6 +242,47 @@ e.g. in the spider's ``__init__`` method::
If you run this spider again then INFO messages from
``scrapy.spidermiddlewares.httperror`` logger will be gone.
+You can also filter log records by :class:`~logging.LogRecord` data. For
+example, you can filter log records by message content using a substring or
+a regular expression. Create a :class:`logging.Filter` subclass
+and equip it with a regular expression pattern to
+filter out unwanted messages::
+
+ import logging
+ import re
+
+ class ContentFilter(logging.Filter):
+ def filter(self, record):
+ match = re.search(r'\d{3} [Ee]rror, retrying', record.message)
+ if match:
+ return False
+
+A project-level filter may be attached to the root
+handler created by Scrapy, this is a wieldy way to
+filter all loggers in different parts of the project
+(middlewares, spider, etc.)::
+
+ import logging
+ import scrapy
+
+ class MySpider(scrapy.Spider):
+ # ...
+ def __init__(self, *args, **kwargs):
+ for handler in logging.root.handlers:
+ handler.addFilter(ContentFilter())
+
+Alternatively, you may choose a specific logger
+and hide it without affecting other loggers::
+
+ import logging
+ import scrapy
+
+ class MySpider(scrapy.Spider):
+ # ...
+ def __init__(self, *args, **kwargs):
+ logger = logging.getLogger('my_logger')
+ logger.addFilter(ContentFilter())
+
scrapy.utils.log module
=======================
diff --git a/docs/topics/media-pipeline.rst b/docs/topics/media-pipeline.rst
index 86550d7a4..46bd2859b 100644
--- a/docs/topics/media-pipeline.rst
+++ b/docs/topics/media-pipeline.rst
@@ -15,7 +15,7 @@ typically you'll either use the Files Pipeline or the Images Pipeline.
Both pipelines implement these features:
* Avoid re-downloading media that was downloaded recently
-* Specifying where to store the media (filesystem directory, Amazon S3 bucket,
+* Specifying where to store the media (filesystem directory, FTP server, Amazon S3 bucket,
Google Cloud Storage bucket)
The Images Pipeline has a few extra functions for processing images:
@@ -56,6 +56,8 @@ this:
error will be logged and the file won't be present in the ``files`` field.
+.. _images-pipeline:
+
Using the Images Pipeline
=========================
@@ -68,14 +70,10 @@ The advantage of using the :class:`ImagesPipeline` for image files is that you
can configure some extra functions like generating thumbnails and filtering
the images based on their size.
-The Images Pipeline uses `Pillow`_ for thumbnailing and normalizing images to
-JPEG/RGB format, so you need to install this library in order to use it.
-`Python Imaging Library`_ (PIL) should also work in most cases, but it is known
-to cause troubles in some setups, so we recommend to use `Pillow`_ instead of
-PIL.
+The Images Pipeline requires Pillow_ 4.0.0 or greater. It is used for
+thumbnailing and normalizing images to JPEG/RGB format.
.. _Pillow: https://github.com/python-pillow/Pillow
-.. _Python Imaging Library: http://www.pythonware.com/products/pil/
.. _topics-media-pipeline-enabling:
@@ -113,25 +111,82 @@ For the Images Pipeline, set the :setting:`IMAGES_STORE` setting::
IMAGES_STORE = '/path/to/valid/dir'
+.. _topics-file-naming:
+
+File Naming
+===========
+
+Default File Naming
+-------------------
+
+By default, files are stored using an `SHA-1 hash`_ of their URLs for the file names.
+
+For example, the following image URL::
+
+ http://www.example.com/image.jpg
+
+Whose ``SHA-1 hash`` is::
+
+ 3afec3b4765f8f0a07b78f98c07b83f013567a0a
+
+Will be downloaded and stored using your chosen :ref:`storage method ` and the following file name::
+
+ 3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
+
+Custom File Naming
+-------------------
+
+You may wish to use a different calculated file name for saved files.
+For example, classifying an image by including meta in the file name.
+
+Customize file names by overriding the ``file_path`` method of your
+media pipeline.
+
+For example, an image pipeline with image URL::
+
+ http://www.example.com/product/images/large/front/0000000004166
+
+Can be processed into a file name with a condensed hash and the perspective
+``front``::
+
+ 00b08510e4_front.jpg
+
+By overriding ``file_path`` like this:
+
+.. code-block:: python
+
+ import hashlib
+ from os.path import splitext
+
+ def file_path(self, request, response=None, info=None, *, item=None):
+ image_url_hash = hashlib.shake_256(request.url.encode()).hexdigest(5)
+ image_perspective = request.url.split('/')[-2]
+ image_filename = f'{image_url_hash}_{image_perspective}.jpg'
+
+ return image_filename
+
+.. warning::
+ If your custom file name scheme relies on meta data that can vary between
+ scrapes it may lead to unexpected re-downloading of existing media using
+ new file names.
+
+ For example, if your custom file name scheme uses a product title and the
+ site changes an item's product title between scrapes, Scrapy will re-download
+ the same media using updated file names.
+
+For more information about the ``file_path`` method, see :ref:`topics-media-pipeline-override`.
+
+.. _topics-supported-storage:
+
Supported Storage
=================
File system storage
-------------------
-The files are stored using a `SHA1 hash`_ of their URLs for the file names.
+File system storage will save files to the following path::
-For example, the following image URL::
-
- http://www.example.com/image.jpg
-
-Whose ``SHA1 hash`` is::
-
- 3afec3b4765f8f0a07b78f98c07b83f013567a0a
-
-Will be downloaded and stored in the following file::
-
- /full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
+ /full/
Where:
@@ -141,6 +196,9 @@ Where:
* ``full`` is a sub-directory to separate full images from thumbnails (if
used). For more info see :ref:`topics-images-thumbnails`.
+* ```` is the file name assigned to the file. For more info see :ref:`topics-file-naming`.
+
+
.. _media-pipeline-ftp:
FTP server storage
@@ -156,7 +214,7 @@ following forms::
ftp://username:password@address:port/path
ftp://address:port/path
-
+
If ``username`` and ``password`` are not provided, they are taken from the :setting:`FTP_USER` and
:setting:`FTP_PASSWORD` settings respectively.
@@ -164,14 +222,17 @@ FTP supports two different connection modes: active or passive. Scrapy uses
the passive connection mode by default. To use the active connection mode instead,
set the :setting:`FEED_STORAGE_FTP_ACTIVE` setting to ``True``.
+.. _media-pipelines-s3:
+
Amazon S3 storage
-----------------
.. setting:: FILES_STORE_S3_ACL
.. setting:: IMAGES_STORE_S3_ACL
-:setting:`FILES_STORE` and :setting:`IMAGES_STORE` can represent an Amazon S3
-bucket. Scrapy will automatically upload the files to the bucket.
+If botocore_ >= 1.4.87 is installed, :setting:`FILES_STORE` and
+:setting:`IMAGES_STORE` can represent an Amazon S3 bucket. Scrapy will
+automatically upload the files to the bucket.
For example, this is a valid :setting:`IMAGES_STORE` value::
@@ -187,8 +248,9 @@ policy::
For more information, see `canned ACLs`_ in the Amazon S3 Developer Guide.
-Because Scrapy uses ``botocore`` internally you can also use other S3-like storages. Storages like
-self-hosted `Minio`_ or `s3.scality`_. All you need to do is set endpoint option in you Scrapy settings::
+You can also use other S3-like storages. Storages like self-hosted `Minio`_ or
+`s3.scality`_. All you need to do is set endpoint option in you Scrapy
+settings::
AWS_ENDPOINT_URL = 'http://minio.example.com:9000'
@@ -197,14 +259,17 @@ For self-hosting you also might feel the need not to use SSL and not to verify S
AWS_USE_SSL = False # or True (None by default)
AWS_VERIFY = False # or True (None by default)
+.. _botocore: https://github.com/boto/botocore
+.. _canned ACLs: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
.. _Minio: https://github.com/minio/minio
.. _s3.scality: https://s3.scality.com/
-.. _canned ACLs: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
+
+
+.. _media-pipeline-gcs:
Google Cloud Storage
---------------------
-.. setting:: GCS_PROJECT_ID
.. setting:: FILES_STORE_GCS_ACL
.. setting:: IMAGES_STORE_GCS_ACL
@@ -243,20 +308,22 @@ Usage example
.. setting:: IMAGES_URLS_FIELD
.. setting:: IMAGES_RESULT_FIELD
-In order to use a media pipeline first, :ref:`enable it
+In order to use a media pipeline, first :ref:`enable it
`.
-Then, if a spider returns a dict with the URLs key (``file_urls`` or
-``image_urls``, for the Files or Images Pipeline respectively), the pipeline will
-put the results under respective key (``files`` or ``images``).
+Then, if a spider returns an :ref:`item object ` with the URLs
+field (``file_urls`` or ``image_urls``, for the Files or Images Pipeline
+respectively), the pipeline will put the results under the respective field
+(``files`` or ``images``).
-If you prefer to use :class:`~.Item`, then define a custom item with the
-necessary fields, like in this example for Images Pipeline::
+When using :ref:`item types ` for which fields are defined beforehand,
+you must define both the URLs field and the results field. For example, when
+using the images pipeline, items must define both the ``image_urls`` and the
+``images`` field. For instance, using the :class:`~scrapy.Item` class::
import scrapy
class MyItem(scrapy.Item):
-
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
@@ -346,9 +413,9 @@ Where:
* ```` is the one specified in the :setting:`IMAGES_THUMBS`
dictionary keys (``small``, ``big``, etc)
-* ```` is the `SHA1 hash`_ of the image url
+* ```` is the `SHA-1 hash`_ of the image url
-.. _SHA1 hash: https://en.wikipedia.org/wiki/SHA_hash_functions
+.. _SHA-1 hash: https://en.wikipedia.org/wiki/SHA_hash_functions
Example of image files stored using ``small`` and ``big`` thumbnail names::
@@ -408,15 +475,16 @@ See here the methods that you can override in your custom Files Pipeline:
.. class:: FilesPipeline
- .. method:: file_path(self, request, response=None, info=None)
+ .. method:: file_path(self, request, response=None, info=None, *, item=None)
This method is called once per downloaded item. It returns the
download path of the file originating from the specified
:class:`response `.
In addition to ``response``, this method receives the original
- :class:`request