from __future__ import annotations import pickle import re import pytest from packaging.version import Version from w3lib import __version__ as w3lib_version from scrapy.http import HtmlResponse, XmlResponse from scrapy.link import Link from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor, LxmlParserLinkExtractor from tests import get_testdata # a hack to skip base class tests in pytest class Base: class TestLinkExtractorBase: extractor_cls: type | None = None def setup_method(self): body = get_testdata("link_extractor", "linkextractor.html") self.response = HtmlResponse(url="http://example.com/index", body=body) def test_urls_type(self): """Test that the resulting urls are str objects""" lx = self.extractor_cls() assert all( isinstance(link.url, str) for link in lx.extract_links(self.response) ) def test_extract_all_links(self): lx = self.extractor_cls() page4_url = "http://example.com/page%204.html" assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), Link( url="http://example.com/sample3.html#foo", text="sample 3 repetition with fragment", ), Link(url="http://www.google.com/something", text=""), Link(url="http://example.com/innertag.html", text="inner tag"), Link(url=page4_url, text="href with whitespaces"), ] def test_extract_filter_allow(self): lx = self.extractor_cls(allow=("sample",)) assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), Link( url="http://example.com/sample3.html#foo", text="sample 3 repetition with fragment", ), ] def test_extract_filter_allow_with_duplicates(self): lx = self.extractor_cls(allow=("sample",), unique=False) assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), Link( url="http://example.com/sample3.html", text="sample 3 repetition", ), Link( url="http://example.com/sample3.html", text="sample 3 repetition", ), Link( url="http://example.com/sample3.html#foo", text="sample 3 repetition with fragment", ), ] def test_extract_filter_allow_with_duplicates_canonicalize(self): lx = self.extractor_cls(allow=("sample",), unique=False, canonicalize=True) assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), Link( url="http://example.com/sample3.html", text="sample 3 repetition", ), Link( url="http://example.com/sample3.html", text="sample 3 repetition", ), Link( url="http://example.com/sample3.html", text="sample 3 repetition with fragment", ), ] def test_extract_filter_allow_no_duplicates_canonicalize(self): lx = self.extractor_cls(allow=("sample",), unique=True, canonicalize=True) assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), ] def test_extract_filter_allow_and_deny(self): lx = self.extractor_cls(allow=("sample",), deny=("3",)) assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), ] def test_extract_filter_allowed_domains(self): lx = self.extractor_cls(allow_domains=("google.com",)) assert list(lx.extract_links(self.response)) == [ Link(url="http://www.google.com/something", text=""), ] def test_extraction_using_single_values(self): """Test the extractor's behaviour among different situations""" lx = self.extractor_cls(allow="sample") assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), Link(url="http://example.com/sample3.html", text="sample 3 text"), Link( url="http://example.com/sample3.html#foo", text="sample 3 repetition with fragment", ), ] lx = self.extractor_cls(allow="sample", deny="3") assert list(lx.extract_links(self.response)) == [ Link(url="http://example.com/sample1.html", text=""), Link(url="http://example.com/sample2.html", text="sample 2"), ] lx = self.extractor_cls(allow_domains="google.com") assert list(lx.extract_links(self.response)) == [ Link(url="http://www.google.com/something", text=""), ] lx = self.extractor_cls(deny_domains="example.com") assert list(lx.extract_links(self.response)) == [ Link(url="http://www.google.com/something", text=""), ] def test_nofollow(self): """Test the extractor's behaviour for links with rel='nofollow'""" html = b"""
'
)
response = HtmlResponse("http://example.com/index.html", body=html)
lx = self.extractor_cls(tags=None)
assert lx.extract_links(response) == []
lx = self.extractor_cls()
assert lx.extract_links(response) == [
Link(url="http://example.com/sample1.html", text=""),
Link(url="http://example.com/sample2.html", text="sample 2"),
]
lx = self.extractor_cls(tags="area")
assert lx.extract_links(response) == [
Link(url="http://example.com/sample1.html", text=""),
]
lx = self.extractor_cls(tags="a")
assert lx.extract_links(response) == [
Link(url="http://example.com/sample2.html", text="sample 2"),
]
lx = self.extractor_cls(
tags=("a", "img"), attrs=("href", "src"), deny_extensions=()
)
assert lx.extract_links(response) == [
Link(url="http://example.com/sample2.html", text="sample 2"),
Link(url="http://example.com/sample2.jpg", text=""),
]
def test_tags_attrs(self):
html = b"""
"""
response = HtmlResponse("http://example.com/index.html", body=html)
lx = self.extractor_cls(tags="div", attrs="data-url")
assert lx.extract_links(response) == [
Link(
url="http://example.com/get?id=1",
text="Item 1",
fragment="",
nofollow=False,
),
Link(
url="http://example.com/get?id=2",
text="Item 2",
fragment="",
nofollow=False,
),
]
lx = self.extractor_cls(tags=("div",), attrs=("data-url",))
assert lx.extract_links(response) == [
Link(
url="http://example.com/get?id=1",
text="Item 1",
fragment="",
nofollow=False,
),
Link(
url="http://example.com/get?id=2",
text="Item 2",
fragment="",
nofollow=False,
),
]
def test_tags_attrs_wildcard_and_deny(self):
html = b"""
a
p
""" response = HtmlResponse("http://example.com/index.html", body=html) def urls(**kwargs): lx = self.extractor_cls(**kwargs) return [link.url for link in lx.extract_links(response)] # Default behavior is unchanged: only the listed tags and attributes. assert urls() == ["http://example.com/a.html"] # "*" as a tag matches every tag. assert urls(tags="*") == [ "http://example.com/a.html", "http://example.com/span.html", ] # "*" as an attribute matches every attribute. assert urls(tags="*", attrs="*") == [ "http://example.com/a.html", "http://example.com/div.html", "http://example.com/span.html", "http://example.com/p.html", ] # deny_tags excludes tags from the wildcard. assert urls(tags="*", attrs="*", deny_tags="div") == [ "http://example.com/a.html", "http://example.com/span.html", "http://example.com/p.html", ] # deny_attrs excludes attributes from the wildcard. assert urls(tags="*", attrs="*", deny_attrs="data-url") == [ "http://example.com/a.html", "http://example.com/span.html", ] # deny_tags also applies when tags are listed explicitly. assert urls(tags=("a", "span"), attrs="*", deny_tags="span") == [ "http://example.com/a.html", ] # The wildcard for one parameter is independent of the other. assert urls(tags="a", attrs="*") == ["http://example.com/a.html"] def test_xhtml(self): xhtml = b"""