diff --git a/scrapy/linkextractors/lxmlhtml.py b/scrapy/linkextractors/lxmlhtml.py index dd8dcdf7c..23cbd0116 100644 --- a/scrapy/linkextractors/lxmlhtml.py +++ b/scrapy/linkextractors/lxmlhtml.py @@ -1,6 +1,7 @@ """ Link extractor based on lxml.html """ +import logging import operator from functools import partial from urllib.parse import urljoin, urlparse @@ -23,6 +24,8 @@ from scrapy.utils.python import unique as unique_list from scrapy.utils.response import get_base_url from scrapy.utils.url import url_has_any_extension, url_is_from_any_domain +logger = logging.getLogger(__name__) + # from lxml/src/lxml/html/__init__.py XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml" @@ -88,7 +91,12 @@ class LxmlParserLinkExtractor: url = self.process_attr(attr_val) if url is None: continue - url = safe_url_string(url, encoding=response_encoding) + try: + url = safe_url_string(url, encoding=response_encoding) + except ValueError: + logger.debug(f"Skipping extraction of link with bad URL {url!r}") + continue + # to fix relative links after process_value url = urljoin(response_url, url) link = Link( diff --git a/tests/test_linkextractors.py b/tests/test_linkextractors.py index f663013ba..e1ec19601 100644 --- a/tests/test_linkextractors.py +++ b/tests/test_linkextractors.py @@ -2,6 +2,10 @@ import pickle import re import unittest +from packaging.version import Version +from pytest import mark +from w3lib import __version__ as w3lib_version + from scrapy.http import HtmlResponse, XmlResponse from scrapy.link import Link from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor @@ -815,3 +819,34 @@ class LxmlLinkExtractorTestCase(Base.LinkExtractorTestCase): def test_restrict_xpaths_with_html_entities(self): super().test_restrict_xpaths_with_html_entities() + + @mark.skipif( + Version(w3lib_version) < Version("2.0.0"), + reason=( + "Before w3lib 2.0.0, w3lib.url.safe_url_string would not complain " + "about an invalid port value." + ), + ) + def test_skip_bad_links(self): + html = b""" + Why would you do this? + Good Link + Good Link 2 + """ + response = HtmlResponse("http://example.org/index.html", body=html) + lx = self.extractor_cls() + self.assertEqual( + [link for link in lx.extract_links(response)], + [ + Link( + url="http://example.org/item2.html", + text="Good Link", + nofollow=False, + ), + Link( + url="http://example.org/item3.html", + text="Good Link 2", + nofollow=False, + ), + ], + ) diff --git a/tox.ini b/tox.ini index d96a278ea..06b52f3dc 100644 --- a/tox.ini +++ b/tox.ini @@ -101,6 +101,7 @@ install_command = python -I -m pip install {opts} {packages} [testenv:pinned] +basepython = python3.7 deps = {[pinned]deps} PyDispatcher==2.0.5