Coverage for src/crawler/by_source/jsig_crawler.py: 10%
107 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-09-23 14:47 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-09-23 14:47 +0000
1from urllib.parse import urljoin
3from bs4 import BeautifulSoup, Tag
4from django.conf import settings
5from ptf.model_data import create_abstract, create_articledata, create_contributor, create_subj
7from crawler.abstract_crawlers.matching_crawler import MatchingCrawler
8from crawler.utils import (
9 add_pdf_link_to_xarticle,
10 add_source_link_to_xarticle,
11 cleanup_str,
12 regex_to_dict,
13)
16class JsigCrawler(MatchingCrawler):
17 source_name = "Journal of Singularities website"
18 source_domain = "JSIG"
19 source_website = "https://journalofsing.org"
21 issue_re = r"volume (?P<volume>\d+), (?P<year>\d{4})"
22 requests_interval = max(getattr(settings, "REQUESTS_INTERVAL", 90), 35)
24 def parse_collection_content(self, content):
25 xissues = []
26 soup = BeautifulSoup(content, "html5lib")
27 issues_tags = soup.select("#navcontainer a[href^='volume']")
29 # issues 1-10 are listed in a dedicated html page
30 nested = soup.select_one("#navcontainer a[href^='volume1-10']")
31 if isinstance(nested, Tag):
32 issues_tags.remove(nested)
33 nested_href = nested.get("href")
34 if isinstance(nested_href, str):
35 additional_content = self.download_file(urljoin(self.collection_url, nested_href))
36 more_soup = BeautifulSoup(additional_content, "html5lib")
37 more_issues = more_soup.select("#col-text-content2 a")
38 issues_tags.extend(more_issues)
40 for tag in issues_tags:
41 issue_dict = regex_to_dict(
42 self.issue_re, tag.text, error_msg="Couldn't parse issue data"
43 )
45 issue_href = tag.get("href")
46 if not isinstance(issue_href, str):
47 raise ValueError("Couldn't parse issue url")
49 xissues.append(
50 self.create_xissue(
51 urljoin(self.collection_url, issue_href),
52 int(issue_dict["year"]),
53 issue_dict["volume"],
54 None,
55 )
56 )
57 return xissues
59 def parse_issue_content(self, content, xissue):
60 if not xissue.url:
61 raise ValueError("xissue must have an url")
63 soup = BeautifulSoup(content, "html5lib")
64 articles_tags = soup.select(
65 "#col-text-content2 a[href$='.html'], #col-text-content2 a[href$='.pdf']"
66 )
67 for index, tag in enumerate(articles_tags):
68 article_url = tag.get("href")
69 if not isinstance(article_url, str):
70 raise ValueError("Couldn't parse article data")
71 xarticle = create_articledata()
72 xarticle.pid = "a" + str(index)
74 if article_url.endswith(".html"):
75 xarticle.url = urljoin(xissue.url, article_url)
76 else:
77 # branch here if article does not have any dedicated html page
78 add_source_link_to_xarticle(xarticle, xissue.url, self.source_domain)
79 add_pdf_link_to_xarticle(xarticle, urljoin(xissue.url, article_url))
80 xarticle.title_tex = tag.text
82 article_table = tag.parent.parent.parent
83 if article_table:
84 authors_tag = article_table.select_one("td[colspan='2'][width='525']")
85 authors_list = authors_tag.text.replace(" and ", ", ").split(", ")
86 for author_str in authors_list:
87 xarticle.contributors.append(
88 create_contributor(string_name=cleanup_str(author_str), role="author")
89 )
90 xissue.articles.append(xarticle)
92 def parse_article_content(self, content, xissue, xarticle, url):
93 if not xarticle.url:
94 raise ValueError("xarticle must have an url")
96 soup = BeautifulSoup(content, "html5lib")
97 soup = soup.select_one("#col-text-content")
99 if not soup:
100 raise ValueError("Couldn't parse article page")
102 # Title and pdf
103 title_tag = soup.select_one("h2")
104 if not title_tag:
105 raise ValueError("Couldn't parse article title")
106 xarticle.title_tex = cleanup_str(title_tag.text)
107 pdf_tag = title_tag.select_one("a")
108 if not pdf_tag:
109 raise ValueError("Couldn't find article pdf")
110 pdf_href = pdf_tag.get("href")
111 if not isinstance(pdf_href, str):
112 raise ValueError("Couldn't parse article pdf")
113 pdf_href = urljoin(xarticle.url, pdf_href)
114 add_pdf_link_to_xarticle(xarticle, pdf_href)
115 title_tag.decompose()
117 # Authors
118 authors_tag = soup.select_one("div > span")
119 if authors_tag:
120 authors_tag.decompose()
122 # MSC
123 msc_header = soup.select_one("p:-soup-contains('Mathematical Subject Classification')")
124 if msc_header:
125 msc_tag = msc_header.findNext("p")
126 if isinstance(msc_tag, Tag):
127 for msc_str in (
128 msc_tag.text.replace(";", ",")
129 .replace("Primary", "")
130 .replace("Secondary", "")
131 .replace(" ", "")
132 .split(",")
133 ):
134 xarticle.kwds.append(create_subj(type="msc", value=cleanup_str(msc_str)))
135 msc_tag.decompose()
136 msc_header.decompose()
138 # Abstract
139 abstract_header = soup.select_one("p:-soup-contains('Abstract')")
140 if abstract_header:
141 abstract_tag = abstract_header.findNext("p")
142 if isinstance(abstract_tag, Tag):
143 xarticle.abstracts.append(
144 create_abstract(value_tex=cleanup_str(abstract_tag.text), lang="en")
145 )
146 abstract_tag.decompose()
147 abstract_header.decompose()
149 # Pages
150 pages_tag = soup.select_one("p.style3")
151 if isinstance(pages_tag, Tag):
152 pages_text = cleanup_str(pages_tag.text)
153 pages_dict = regex_to_dict(
154 r"volume \d+ \(\d+\), (?P<fpage>\d+)\-(?P<lpage>\d+)", pages_text
155 )
156 xarticle.fpage = pages_dict["fpage"]
157 xarticle.lpage = pages_dict["lpage"]
158 pages_tag.decompose()
160 # DOI
161 doi_tag = soup.select_one("p:-soup-contains('DOI:')")
162 if isinstance(doi_tag, Tag):
163 xarticle.doi = cleanup_str(
164 doi_tag.select_one("a").get("href").removeprefix("http://dx.doi.org/")
165 )
166 doi_tag.decompose()
167 if xarticle.url == "https://journalofsing.org/volume22/article2.html":
168 xarticle.doi = "10.5427/jsing.2020.22b"
170 return xarticle