Skip to content

Commit 2700809

Browse files
committed
Stop declaring requests directly and fix the sources that used it
The scraper already brings requests[socks] in, so lncrawl declaring it too pinned a version it does not own. The browser middleware now uses starlette's own mutable headers, and the four sources that reached past the scraper go through it: chireads and wtrlab work as before, fenrirscan keeps returning nothing rather than raising, and totallytranslations no longer replaces its session with one that has no scraper methods. Base URLs are deduplicated in declared order with https first, instead of through a set. base_url[0] is home_url and the scraper's origin, so which address that was changed between runs of identical code, and three sources defaulted to plaintext http.
1 parent bdcccba commit 2700809

9 files changed

Lines changed: 33 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
151151
because the default cannot handle their markup; the value was stored on the crawler
152152
and never reached the session that builds the soup.
153153

154+
- **A source's primary address no longer changes between runs.** Where a source lists
155+
several addresses, the one treated as its home was picked from an unordered set, so it
156+
varied from one run to the next for the same unchanged code — and a source listing both
157+
`http` and `https` for the same site could end up pinned to the plaintext one. Secure
158+
addresses now come first, the source's own order is kept within that, and three sources
159+
stop defaulting to `http`. This also makes the generated source index reproducible: it
160+
was rewriting a hundred lines on every build with nothing behind it.
161+
162+
- **`chireads` search results had no titles.** The site moved the title out of the
163+
attribute the source read; it now reads the one the site actually emits.
164+
`totallytranslations` replaced its own HTTP session on startup, which broke every
165+
request it then made — that domain has since lapsed and was already flagged as
166+
unavailable, so this only removes the broken code.
167+
154168
- **Asking what is known about a site no longer records that it was asked.** Reading a
155169
site's diagnosis stored a profile for it, so browsing the sources list wrote one entry
156170
per site looked at — and with the store bounded, that could push out what a running

lncrawl/server/middleware/browser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import pickle
66
from urllib.parse import urljoin, urlparse
77

8-
from requests.structures import CaseInsensitiveDict
98
from scraper import Scraper
109
from scraper.exceptions import Blocked
10+
from starlette.datastructures import MutableHeaders
1111
from starlette.requests import Request
1212
from starlette.responses import RedirectResponse, Response
1313

@@ -78,7 +78,7 @@ async def _proxy(self, scraper: Scraper, path: str, request: Request) -> Respons
7878

7979
body = await request.body()
8080

81-
headers = CaseInsensitiveDict(request.headers)
81+
headers = MutableHeaders(headers=dict(request.headers))
8282
if headers.get("host"):
8383
headers["host"] = apex_domain
8484
if headers.get("origin"):

lncrawl/services/jobs/service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Any, Callable, Iterable, List, Optional, TypeVar, Union
3+
from typing import Any, Callable, Dict, Iterable, List, Optional, TypeVar, Union
44

55
from sqlalchemy.orm import aliased
66
import sqlmodel as sq
@@ -1002,20 +1002,20 @@ def _fail(
10021002
done=Job.done + pending,
10031003
failed=Job.failed + pending,
10041004
)
1005-
extra = self._get_extra(sess, job_id, extra)
10061005
self._update(
10071006
sess,
10081007
job_id,
10091008
error=reason,
10101009
status=JobStatus.FAILED,
1010+
extra=self._get_extra(sess, job_id, extra),
10111011
)
10121012

10131013
def _get_extra(
10141014
self,
10151015
sess: Session,
10161016
job_id: str,
10171017
updates: Union[dict, Callable[[dict], None]],
1018-
) -> None:
1018+
) -> Dict[str, Any]:
10191019
current = sess.scalar(sq.select(Job.extra).where(Job.id == job_id))
10201020

10211021
extra = dict(current or {})

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ dependencies = [
5656
"exejs>=0.0.7",
5757
"readability-lxml>=0.8.0,<1.0.0",
5858
"regex>=2024.0.0",
59-
"requests[socks]>=2.32.0",
6059
"sqlmodel>=0.0.22",
6160
"tqdm>=4.66.0,<5.0.0",
6261
"typer>=0.12.0",

sources/en/t/totallytranslations.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# -*- coding: utf-8 -*-
22
import logging
33

4-
from requests.sessions import Session
5-
64
from lncrawl.core import Chapter, LegacyCrawler
75

86
logger = logging.getLogger(__name__)
@@ -11,9 +9,6 @@
119
class TotallyTranslations(LegacyCrawler):
1210
base_url = "https://totallytranslations.com/"
1311

14-
def initialize(self):
15-
self.scraper = Session()
16-
1712
def read_novel_info(self):
1813
logger.debug("Visiting %s", self.novel_url)
1914
soup = self.get_soup(self.novel_url)

sources/fr/chireads.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import logging
44

5-
import requests
6-
75
from lncrawl.core import Chapter, LegacyCrawler
86

97
logger = logging.getLogger(__name__)
@@ -17,19 +15,17 @@ class Chireads(LegacyCrawler):
1715
def search_novel(self, query):
1816
query = query.lower().replace(" ", "+")
1917

20-
# NOTE: Using self.get_soup() here throw an error, I don't know why.
21-
response = requests.get("https://chireads.com/search?x=0&y=0&name=" + query)
22-
soup = self.make_soup(response)
18+
soup = self.get_soup("https://chireads.com/search?x=0&y=0&name=" + query)
2319

2420
result = []
2521
content = soup.find("div", {"id": "content"})
2622

2723
for novel in content.find_all("li"):
28-
content = novel.find("a")
24+
link = novel.find("a")
2925
result.append(
3026
{
31-
"title": content.get("title"),
32-
"url": self.absolute_url(content.get("href")),
27+
"title": link.get("aria-label"),
28+
"url": self.absolute_url(link.get("href")),
3329
}
3430
)
3531

sources/multi/wtrlab.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import List, Union
77

88
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
9-
import requests
109

1110
from lncrawl.core import Chapter, LegacyCrawler, PageSoup, SearchResult
1211

@@ -25,10 +24,10 @@ def _parse_next_data(self, soup: PageSoup):
2524
return json.loads(metadata_json.get_text(strip=True))
2625

2726
def search_novel(self, query: str):
28-
novels = requests.post(
27+
novels = self.post_json(
2928
"https://www.wtr-lab.com/api/search",
3029
json={"text": query},
31-
).json()
30+
)
3231
logger.info("Search results: %s", novels)
3332

3433
results = []

sources/tr/fenrirscan.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import logging
33
import re
44

5-
import requests
6-
75
from lncrawl.core import Chapter, LegacyCrawler, SearchResult
86

97
logger = logging.getLogger(__name__)
@@ -16,16 +14,17 @@ class FenrirScans(LegacyCrawler):
1614
has_mtl = False
1715

1816
def search_novel(self, query):
19-
"""
20-
Uses the site's AJAX search endpoint to find novels.
21-
"""
22-
# Prepare payload for AJAX search
17+
# The site no longer answers this: its theme registers only next/prev/tepki, and
18+
# `ts_ac_do_search` returns `400`. Its `?s=` page is no better — the grid it
19+
# renders is byte-identical for every query, including one that matches nothing.
20+
# Left in place because a restored endpoint would work again as written, and
21+
# returning nothing is what this did before the request began raising.
2322
data = {
2423
"action": "ts_ac_do_search",
2524
"ts_ac_query": query,
2625
}
27-
response = requests.post(self.search_url, data=data)
2826
try:
27+
response = self.post_response(self.search_url, data=data)
2928
return [
3029
SearchResult(
3130
title=item["post_title"],
@@ -37,9 +36,6 @@ def search_novel(self, query):
3736
return []
3837

3938
def read_novel_info(self):
40-
"""
41-
Parses novel metadata and chapter list from the novel page.
42-
"""
4339
soup = self.get_soup(self.novel_url)
4440
# Metadata
4541
title = soup.find("h1")
@@ -66,8 +62,10 @@ def read_novel_info(self):
6662

6763
# Chapters
6864
chapter_links = soup.find_all("a", href=re.compile(r"-bolum-\d+"))
65+
6966
# Reverse so earliest chapters first
7067
chapter_links = list(reversed(chapter_links))
68+
7169
for idx, a in enumerate(chapter_links, 1):
7270
chap_url = self.absolute_url(a["href"])
7371
chap_title = (a.find("span", {"class": "chapternum"}) or a).text

uv.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)