Refactor HTTP common code (#207)
- move to common/http.py, - make fetch_url return a Response.
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
from xml.dom.minidom import parseString
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
"""Fetch versions with their dates from the RSS feed of
|
"""Fetch versions with their dates from the RSS feed of
|
||||||
https://docs.aws.amazon.com/neptune/latest/userguide/engine-releases.html.
|
https://docs.aws.amazon.com/neptune/latest/userguide/engine-releases.html.
|
||||||
@@ -14,8 +15,8 @@ URL = "https://docs.aws.amazon.com/neptune/latest/userguide/rssupdates.rss"
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
rss = parseString(response)
|
rss = parseString(response.text)
|
||||||
for item in rss.getElementsByTagName("item"):
|
for item in rss.getElementsByTagName("item"):
|
||||||
title = item.getElementsByTagName("title")[0].firstChild.nodeValue
|
title = item.getElementsByTagName("title")[0].firstChild.nodeValue
|
||||||
pubDate = item.getElementsByTagName("pubDate")[0].firstChild.nodeValue
|
pubDate = item.getElementsByTagName("pubDate")[0].firstChild.nodeValue
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ URLS = [
|
|||||||
"https://support.apple.com/kb/HT205759", # 2013
|
"https://support.apple.com/kb/HT205759", # 2013
|
||||||
"https://support.apple.com/kb/HT204611", # 2011 to 2012
|
"https://support.apple.com/kb/HT204611", # 2011 to 2012
|
||||||
# Apple still links to the following articles, but they are 404:
|
# Apple still links to the following articles, but they are 404:
|
||||||
# Disabled, too much timed out.
|
|
||||||
"http://web.archive.org/web/20230404214605_/https://support.apple.com/en-us/HT5165", # 2010
|
"http://web.archive.org/web/20230404214605_/https://support.apple.com/en-us/HT5165", # 2010
|
||||||
"http://web.archive.org/web/20230327200842_/https://support.apple.com/en-us/HT4218", # 2008-2009
|
"http://web.archive.org/web/20230327200842_/https://support.apple.com/en-us/HT4218", # 2008-2009
|
||||||
"http://web.archive.org/web/20230204234533_/https://support.apple.com/en-us/HT1263", # 2005-2007
|
"http://web.archive.org/web/20230204234533_/https://support.apple.com/en-us/HT1263", # 2005-2007
|
||||||
@@ -54,7 +54,7 @@ def parse_date(date_str):
|
|||||||
print("::group::apple")
|
print("::group::apple")
|
||||||
versions_by_product = {k: {} for k in CONFIG.keys()}
|
versions_by_product = {k: {} for k in CONFIG.keys()}
|
||||||
|
|
||||||
for response in endoflife.fetch_urls(URLS):
|
for response in http.fetch_urls(URLS):
|
||||||
soup = BeautifulSoup(response.text, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
versions_table = soup.find(id="tableWraper")
|
versions_table = soup.find(id="tableWraper")
|
||||||
versions_table = versions_table if versions_table else soup.find('table', class_="gb-table")
|
versions_table = versions_table if versions_table else soup.find('table', class_="gb-table")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -37,10 +38,9 @@ except FileNotFoundError:
|
|||||||
releases_data = {}
|
releases_data = {}
|
||||||
print(f"{PRODUCT} file not found, real release dates will not be used.")
|
print(f"{PRODUCT} file not found, real release dates will not be used.")
|
||||||
|
|
||||||
response = endoflife.fetch_url(URL)
|
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
|
response = http.fetch_url(URL)
|
||||||
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for row in soup.find_all("tr"):
|
for row in soup.find_all("tr"):
|
||||||
cells = row.find_all("td")
|
cells = row.find_all("td")
|
||||||
if len(cells) == 6: # Supported Runtimes
|
if len(cells) == 6: # Supported Runtimes
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
from liquid import Template
|
from liquid import Template
|
||||||
@@ -14,15 +15,12 @@ do not support partial clone so we cannot.
|
|||||||
|
|
||||||
METHOD = 'cgit'
|
METHOD = 'cgit'
|
||||||
|
|
||||||
def make_bs_request(url):
|
|
||||||
response = endoflife.fetch_url(url + '/refs/tags')
|
|
||||||
return BeautifulSoup(response, features="html5lib")
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_releases(url, regex, template):
|
def fetch_releases(url, regex, template):
|
||||||
releases = {}
|
releases = {}
|
||||||
|
|
||||||
soup = make_bs_request(url)
|
response = http.fetch_url(url + '/refs/tags')
|
||||||
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
l_template = Template(template)
|
l_template = Template(template)
|
||||||
|
|
||||||
for table in soup.find_all("table", class_="list"):
|
for table in soup.find_all("table", class_="list"):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ REGEX = r"[r|R]elease [d|D]ate[,|:]? (.*?)\).*?Build Number: (.*?)$"
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = RELEASE_DATES | {}
|
versions = RELEASE_DATES | {}
|
||||||
|
|
||||||
for response in endoflife.fetch_urls(URLS):
|
for response in http.fetch_urls(URLS):
|
||||||
soup = BeautifulSoup(response.text, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for p in soup.findAll("div", class_="text"):
|
for p in soup.findAll("div", class_="text"):
|
||||||
text = p.get_text().strip().replace('\xa0', ' ')
|
text = p.get_text().strip().replace('\xa0', ' ')
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import frontmatter
|
import frontmatter
|
||||||
from concurrent.futures import as_completed
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from os import path
|
from os import path
|
||||||
from requests import Response
|
|
||||||
from requests.adapters import HTTPAdapter
|
|
||||||
from requests.exceptions import ChunkedEncodingError
|
|
||||||
from requests_futures.sessions import FuturesSession
|
|
||||||
from urllib3.util import Retry
|
|
||||||
|
|
||||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent.
|
|
||||||
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0'
|
|
||||||
|
|
||||||
# Handle versions having at least 2 digits (ex. 1.2) and at most 4 digits (ex. 1.2.3.4), with an optional leading "v".
|
# Handle versions having at least 2 digits (ex. 1.2) and at most 4 digits (ex. 1.2.3.4), with an optional leading "v".
|
||||||
# Major version must be >= 1.
|
# Major version must be >= 1.
|
||||||
@@ -49,28 +40,6 @@ def list_products(method, products_filter=None, pathname="website/products") ->
|
|||||||
|
|
||||||
|
|
||||||
# Keep the default timeout high enough to avoid errors with web.archive.org.
|
# Keep the default timeout high enough to avoid errors with web.archive.org.
|
||||||
def fetch_urls(urls, data=None, headers=None, max_retries=10, backoff_factor=0.5, timeout=30) -> list[Response]:
|
|
||||||
try:
|
|
||||||
with FuturesSession() as session:
|
|
||||||
adapter = HTTPAdapter(max_retries=Retry(total=max_retries, backoff_factor=backoff_factor))
|
|
||||||
session.mount('http://', adapter)
|
|
||||||
session.mount('https://', adapter)
|
|
||||||
|
|
||||||
headers = {'User-Agent': USER_AGENT} | ({} if headers is None else headers)
|
|
||||||
futures = [session.get(url, headers=headers, data=data, timeout=timeout, stream=None) for url in urls]
|
|
||||||
return [future.result() for future in as_completed(futures)]
|
|
||||||
except ChunkedEncodingError as e: # See https://github.com/psf/requests/issues/4771#issue-354077499
|
|
||||||
next_max_retries = max_retries - 1
|
|
||||||
if next_max_retries == 0:
|
|
||||||
raise e # So that the function does not get stuck in an infinite loop.
|
|
||||||
else:
|
|
||||||
# We could wait a bit before retrying, but it's not clear if it would help.
|
|
||||||
print(f"Got ChunkedEncodingError while fetching {urls} ({e}), retrying (remaining retries = {next_max_retries}).")
|
|
||||||
return fetch_urls(urls, data, headers, next_max_retries, backoff_factor, timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_url(url, data=None, headers=None, max_retries=5, backoff_factor=0.5, timeout=30) -> str:
|
|
||||||
return fetch_urls([url], data, headers, max_retries, backoff_factor, timeout)[0].text
|
|
||||||
|
|
||||||
|
|
||||||
def write_releases(product, releases, pathname="releases") -> None:
|
def write_releases(product, releases, pathname="releases") -> None:
|
||||||
|
|||||||
33
src/common/http.py
Normal file
33
src/common/http.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from concurrent.futures import as_completed
|
||||||
|
from requests import Response
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from requests.exceptions import ChunkedEncodingError
|
||||||
|
from requests_futures.sessions import FuturesSession
|
||||||
|
from urllib3.util import Retry
|
||||||
|
|
||||||
|
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent.
|
||||||
|
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0'
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_urls(urls, data=None, headers=None, max_retries=10, backoff_factor=0.5, timeout=30) -> list[Response]:
|
||||||
|
try:
|
||||||
|
with FuturesSession() as session:
|
||||||
|
adapter = HTTPAdapter(max_retries=Retry(total=max_retries, backoff_factor=backoff_factor))
|
||||||
|
session.mount('http://', adapter)
|
||||||
|
session.mount('https://', adapter)
|
||||||
|
|
||||||
|
headers = {'User-Agent': USER_AGENT} | ({} if headers is None else headers)
|
||||||
|
futures = [session.get(url, headers=headers, data=data, timeout=timeout, stream=None) for url in urls]
|
||||||
|
return [future.result() for future in as_completed(futures)]
|
||||||
|
except ChunkedEncodingError as e: # See https://github.com/psf/requests/issues/4771#issue-354077499
|
||||||
|
next_max_retries = max_retries - 1
|
||||||
|
if next_max_retries == 0:
|
||||||
|
raise e # So that the function does not get stuck in an infinite loop.
|
||||||
|
else:
|
||||||
|
# We could wait a bit before retrying, but it's not clear if it would help.
|
||||||
|
print(f"Got ChunkedEncodingError while fetching {urls} ({e}), retrying (remaining retries = {next_max_retries}).")
|
||||||
|
return fetch_urls(urls, data, headers, next_max_retries, backoff_factor, timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_url(url, data=None, headers=None, max_retries=5, backoff_factor=0.5, timeout=30) -> Response:
|
||||||
|
return fetch_urls([url], data, headers, max_retries, backoff_factor, timeout)[0]
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -8,15 +9,15 @@ REGEX = r"^(cos-\d+-\d+-\d+-\d+)"
|
|||||||
|
|
||||||
|
|
||||||
def list_milestones():
|
def list_milestones():
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
milestones = soup.find_all('td', string=re.compile(r'COS \d+ LTS'))
|
milestones = soup.find_all('td', string=re.compile(r'COS \d+ LTS'))
|
||||||
return [m.text.split(' ')[1] for m in milestones]
|
return [m.text.split(' ')[1] for m in milestones]
|
||||||
|
|
||||||
|
|
||||||
def fetch_milestones(milestones):
|
def fetch_milestones(milestones):
|
||||||
urls = [f"{URL}m{channel}" for channel in milestones]
|
urls = [f"{URL}m{channel}" for channel in milestones]
|
||||||
return endoflife.fetch_urls(urls)
|
return http.fetch_urls(urls)
|
||||||
|
|
||||||
|
|
||||||
def parse_date(date_str):
|
def parse_date(date_str):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ FIXED_VERSIONS = {
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(f"{URLS}/current/install/install-intro.html")
|
response = http.fetch_url(f"{URLS}/current/install/install-intro.html")
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
minor_versions = [options.attrs["value"] for options in soup.find(class_="version_list").find_all("option")]
|
minor_versions = [options.attrs["value"] for options in soup.find(class_="version_list").find_all("option")]
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ for minor in minor_versions:
|
|||||||
versions[minor + '.0'] = 'N/A'
|
versions[minor + '.0'] = 'N/A'
|
||||||
|
|
||||||
minor_version_urls = [f"{URLS}/{minor}/release-notes/relnotes.html" for minor in minor_versions]
|
minor_version_urls = [f"{URLS}/{minor}/release-notes/relnotes.html" for minor in minor_versions]
|
||||||
for response in endoflife.fetch_urls(minor_version_urls):
|
for response in http.fetch_urls(minor_version_urls):
|
||||||
soup = BeautifulSoup(response.text, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for title in soup.find_all("h2"):
|
for title in soup.find_all("h2"):
|
||||||
versionAndDate = title.get_text().strip()
|
versionAndDate = title.get_text().strip()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
from liquid import Template
|
from liquid import Template
|
||||||
|
|
||||||
@@ -27,8 +28,8 @@ def fetch_releases(distrowatch_id, regex, template):
|
|||||||
releases = {}
|
releases = {}
|
||||||
l_template = Template(template)
|
l_template = Template(template)
|
||||||
url = f"https://distrowatch.com/index.php?distribution={distrowatch_id}"
|
url = f"https://distrowatch.com/index.php?distribution={distrowatch_id}"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for table in soup.select("td.News1>table.News"):
|
for table in soup.select("td.News1>table.News"):
|
||||||
headline = table.select_one("td.NewsHeadline a[href]").get_text().strip()
|
headline = table.select_one("td.NewsHeadline a[href]").get_text().strip()
|
||||||
date = table.select_one("td.NewsDate").get_text()
|
date = table.select_one("td.NewsDate").get_text()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
METHOD = "docker_hub"
|
METHOD = "docker_hub"
|
||||||
@@ -10,8 +10,8 @@ def fetch_releases(url, regex, releases):
|
|||||||
if not isinstance(regex, list):
|
if not isinstance(regex, list):
|
||||||
regex = [regex]
|
regex = [regex]
|
||||||
|
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
data = json.loads(response)
|
data = response.json()
|
||||||
for result in data["results"]:
|
for result in data["results"]:
|
||||||
version = result["name"]
|
version = result["name"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ def parse_platforms_pages():
|
|||||||
all_versions = {}
|
all_versions = {}
|
||||||
print("::group::eks")
|
print("::group::eks")
|
||||||
for url in URLS:
|
for url in URLS:
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for tr in soup.select("#main-col-body")[0].findAll("tr"):
|
for tr in soup.select("#main-col-body")[0].findAll("tr"):
|
||||||
td = tr.find("td")
|
td = tr.find("td")
|
||||||
if td and re.match(endoflife.DEFAULT_VERSION_REGEX, td.text.strip()):
|
if td and re.match(endoflife.DEFAULT_VERSION_REGEX, td.text.strip()):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -18,11 +19,11 @@ def format_date(text: str) -> str:
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
ff_releases = BeautifulSoup(response, features="html5lib").find_all("ol", class_="c-release-list")
|
ff_releases = BeautifulSoup(response.text, features="html5lib").find_all("ol", class_="c-release-list")
|
||||||
urls = [urllib.parse.urljoin(URL, p.get("href")) for p in ff_releases[0].find_all("a")]
|
urls = [urllib.parse.urljoin(URL, p.get("href")) for p in ff_releases[0].find_all("a")]
|
||||||
|
|
||||||
for response in endoflife.fetch_urls(urls):
|
for response in http.fetch_urls(urls):
|
||||||
soup = BeautifulSoup(response.text, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
version = response.request.url.split("/")[-3]
|
version = response.request.url.split("/")[-3]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -10,8 +11,8 @@ CHANNELS = ['nochannel', 'stable', 'regular', 'rapid']
|
|||||||
|
|
||||||
def fetch_channel(channel):
|
def fetch_channel(channel):
|
||||||
url = f"https://cloud.google.com/kubernetes-engine/docs/release-notes-{channel}"
|
url = f"https://cloud.google.com/kubernetes-engine/docs/release-notes-{channel}"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
return BeautifulSoup(response, features="html5lib")
|
return BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
|
|
||||||
def parse_soup_for_versions(soup):
|
def parse_soup_for_versions(soup):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ def split_versions(text):
|
|||||||
return text.replace("GraalVM for JDK ", "jdk-").split(", ")
|
return text.replace("GraalVM for JDK ", "jdk-").split(", ")
|
||||||
|
|
||||||
print("::group::graalvm")
|
print("::group::graalvm")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
for tr in soup.findAll("table")[1].find("tbody").findAll("tr"):
|
for tr in soup.findAll("table")[1].find("tbody").findAll("tr"):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
"""Fetch HAProxy versions with their dates from https://www.haproxy.org/.
|
"""Fetch HAProxy versions with their dates from https://www.haproxy.org/.
|
||||||
@@ -14,8 +15,8 @@ VERSION_REGEX = r"^(\d{4})\/(\d{2})\/(\d{2})\s+:\s+(\d+\.\d+\.\d.?)$"
|
|||||||
def fetch_cycles():
|
def fetch_cycles():
|
||||||
cycles = []
|
cycles = []
|
||||||
|
|
||||||
response = endoflife.fetch_url('https://www.haproxy.org/download/')
|
response = http.fetch_url('https://www.haproxy.org/download/')
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for link in soup.select("a"):
|
for link in soup.select("a"):
|
||||||
m = re.match(CYCLE_REGEX, link.attrs["href"])
|
m = re.match(CYCLE_REGEX, link.attrs["href"])
|
||||||
if m:
|
if m:
|
||||||
@@ -32,7 +33,7 @@ def fetch_releases(cycles):
|
|||||||
releases = {}
|
releases = {}
|
||||||
|
|
||||||
urls = [f"https://www.haproxy.org/download/{cycle}/src/CHANGELOG" for cycle in cycles]
|
urls = [f"https://www.haproxy.org/download/{cycle}/src/CHANGELOG" for cycle in cycles]
|
||||||
for response in endoflife.fetch_urls(urls):
|
for response in http.fetch_urls(urls):
|
||||||
for line in response.text.split('\n'):
|
for line in response.text.split('\n'):
|
||||||
m = re.match(VERSION_REGEX, line)
|
m = re.match(VERSION_REGEX, line)
|
||||||
if m:
|
if m:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -6,8 +7,8 @@ PRODUCT = "ibm-aix"
|
|||||||
URL = "https://www.ibm.com/support/pages/aix-support-lifecycle-information"
|
URL = "https://www.ibm.com/support/pages/aix-support-lifecycle-information"
|
||||||
|
|
||||||
def fetch_releases():
|
def fetch_releases():
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
releases = {}
|
releases = {}
|
||||||
# for all release tables
|
# for all release tables
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
@@ -16,8 +17,8 @@ VERSION_PATTERN = re.compile(r"Looker\s+(?P<version>\d+\.\d+)", re.IGNORECASE)
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
rss = parseString(response)
|
rss = parseString(response.text)
|
||||||
for item in rss.getElementsByTagName("entry"):
|
for item in rss.getElementsByTagName("entry"):
|
||||||
date = dates.parse_datetime(item.getElementsByTagName("updated")[0].firstChild.nodeValue).strftime("%Y-%m-%d")
|
date = dates.parse_datetime(item.getElementsByTagName("updated")[0].firstChild.nodeValue).strftime("%Y-%m-%d")
|
||||||
content = item.getElementsByTagName("content")[0].firstChild.nodeValue
|
content = item.getElementsByTagName("content")[0].firstChild.nodeValue
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
METHOD = "maven"
|
METHOD = "maven"
|
||||||
@@ -17,8 +17,8 @@ def valid_version(version):
|
|||||||
|
|
||||||
def fetch_json(group_id, artifact_id, start):
|
def fetch_json(group_id, artifact_id, start):
|
||||||
url = f"https://search.maven.org/solrsearch/select?q=g:{group_id}+AND+a:{artifact_id}&core=gav&rows=100&wt=json&start={start}"
|
url = f"https://search.maven.org/solrsearch/select?q=g:{group_id}+AND+a:{artifact_id}&core=gav&rows=100&wt=json&start={start}"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
return json.loads(response)
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
def fetch_releases(package_identifier):
|
def fetch_releases(package_identifier):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
METHOD = "npm"
|
METHOD = "npm"
|
||||||
@@ -13,8 +13,8 @@ def fetch_releases(npm_id, regex):
|
|||||||
regex = [regex]
|
regex = [regex]
|
||||||
|
|
||||||
url = f"https://registry.npmjs.org/{npm_id}"
|
url = f"https://registry.npmjs.org/{npm_id}"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
data = json.loads(response)
|
data = response.json()
|
||||||
for version in data["time"]:
|
for version in data["time"]:
|
||||||
matches = False
|
matches = False
|
||||||
for r in regex:
|
for r in regex:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import json
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
"""Fetch Nutanix products versions with their dates from https://portal.nutanix.com/api/v1.
|
"""Fetch Nutanix products versions with their dates from https://portal.nutanix.com/api/v1.
|
||||||
@@ -17,8 +17,8 @@ def fetch_releases(product_code):
|
|||||||
versions = {}
|
versions = {}
|
||||||
url = BASE_URL + product_code
|
url = BASE_URL + product_code
|
||||||
print(url)
|
print(url)
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
data = json.loads(response)
|
data = response.json()
|
||||||
|
|
||||||
for version_data in data["contents"]:
|
for version_data in data["contents"]:
|
||||||
if 'GENERAL_AVAILABILITY' in version_data:
|
if 'GENERAL_AVAILABILITY' in version_data:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
URL = "https://www.paloaltonetworks.com/services/support/end-of-life-announcements/end-of-life-summary"
|
URL = "https://www.paloaltonetworks.com/services/support/end-of-life-announcements/end-of-life-summary"
|
||||||
@@ -15,8 +16,8 @@ def update_releases(html_identifier, file):
|
|||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
print(f"::group::{html_identifier}")
|
print(f"::group::{html_identifier}")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
table = soup.find(id=html_identifier)
|
table = soup.find(id=html_identifier)
|
||||||
for tr in table.findAll("tr")[3:]:
|
for tr in table.findAll("tr")[3:]:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import json
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ PHP_MAJOR_VERSIONS = [4, 5, 7, 8]
|
|||||||
|
|
||||||
def fetch_versions(major_version):
|
def fetch_versions(major_version):
|
||||||
url = f"https://www.php.net/releases/index.php?json&max=-1&version={major_version}"
|
url = f"https://www.php.net/releases/index.php?json&max=-1&version={major_version}"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
data = json.loads(response)
|
data = response.json()
|
||||||
for v in data:
|
for v in data:
|
||||||
data[v] = dates.parse_date(data[v]["date"]).strftime("%Y-%m-%d")
|
data[v] = dates.parse_date(data[v]["date"]).strftime("%Y-%m-%d")
|
||||||
print(f"{v}: {data[v]}")
|
print(f"{v}: {data[v]}")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -7,8 +8,8 @@ PRODUCT = "plesk"
|
|||||||
|
|
||||||
|
|
||||||
def make_bs_request(url):
|
def make_bs_request(url):
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
return BeautifulSoup(response, features="html5lib")
|
return BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
|
|
||||||
# Only 18.0.20.3 and later will be picked up :
|
# Only 18.0.20.3 and later will be picked up :
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ def fetch_releases(pypi_id, regex):
|
|||||||
regex = [regex]
|
regex = [regex]
|
||||||
|
|
||||||
url = f"https://pypi.org/pypi/{pypi_id}/json"
|
url = f"https://pypi.org/pypi/{pypi_id}/json"
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
data = json.loads(response)
|
data = response.json()
|
||||||
for version in data["releases"]:
|
for version in data["releases"]:
|
||||||
R = data["releases"][version]
|
R = data["releases"][version]
|
||||||
matches = False
|
matches = False
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@ for db, url in DBS.items():
|
|||||||
print(f"::group::{db}")
|
print(f"::group::{db}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
for table in soup.find_all("table"):
|
for table in soup.find_all("table"):
|
||||||
for row in table.find_all("tr"):
|
for row in table.find_all("tr"):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
"""Fetch versions with their dates from access.redhat.com.
|
"""Fetch versions with their dates from access.redhat.com.
|
||||||
@@ -13,8 +14,8 @@ URL = "https://access.redhat.com/articles/1365633"
|
|||||||
regex = r"^Satellite (?P<version>\d+\.\d+\.\d+([.-]\d+)?) ([Uu]pdate|[Rr]elease)$"
|
regex = r"^Satellite (?P<version>\d+\.\d+\.\d+([.-]\d+)?) ([Uu]pdate|[Rr]elease)$"
|
||||||
|
|
||||||
print("::group::redhat-satellite")
|
print("::group::redhat-satellite")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
for table in soup.findAll("tbody"):
|
for table in soup.findAll("tbody"):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
URL = "https://access.redhat.com/articles/3078"
|
URL = "https://access.redhat.com/articles/3078"
|
||||||
@@ -7,8 +8,8 @@ URL = "https://access.redhat.com/articles/3078"
|
|||||||
regex = r"RHEL (?P<major>\d)(\. ?(?P<minor>\d+))?(( Update (?P<minor2>\d))| GA)?"
|
regex = r"RHEL (?P<major>\d)(\. ?(?P<minor>\d+))?(( Update (?P<minor2>\d))| GA)?"
|
||||||
|
|
||||||
print("::group::rhel")
|
print("::group::rhel")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
for tr in soup.findAll("tr"):
|
for tr in soup.findAll("tr"):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ def parse_markdown_table(table_text):
|
|||||||
|
|
||||||
|
|
||||||
print("::group::rockylinux")
|
print("::group::rockylinux")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
versions = parse_markdown_table(response)
|
versions = parse_markdown_table(response.text)
|
||||||
endoflife.write_releases('rockylinux', versions)
|
endoflife.write_releases('rockylinux', versions)
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
URL = "https://wiki.ros.org/Distributions"
|
URL = "https://wiki.ros.org/Distributions"
|
||||||
@@ -8,8 +9,8 @@ URL = "https://wiki.ros.org/Distributions"
|
|||||||
regex = r"^ROS (?P<name>(\w| )+)"
|
regex = r"^ROS (?P<name>(\w| )+)"
|
||||||
|
|
||||||
print("::group::ros")
|
print("::group::ros")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
for tr in soup.findAll("tr"):
|
for tr in soup.findAll("tr"):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -11,8 +12,8 @@ def strip_version(version_str):
|
|||||||
|
|
||||||
|
|
||||||
def fetch_releases():
|
def fetch_releases():
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
products_table = soup.find("tbody", id="productSupportLifecycle")
|
products_table = soup.find("tbody", id="productSupportLifecycle")
|
||||||
# Get rows with SLES products
|
# Get rows with SLES products
|
||||||
sles_header_rows = products_table.find_all("tr", class_="row", attrs={"data-productfilter": "SUSE Linux Enterprise Server"})
|
sles_header_rows = products_table.find_all("tr", class_="row", attrs={"data-productfilter": "SUSE Linux Enterprise Server"})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@ def get_latest_minor_versions(versions):
|
|||||||
|
|
||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = dict()
|
versions = dict()
|
||||||
main = endoflife.fetch_url(URL)
|
main = http.fetch_url(URL)
|
||||||
soup = BeautifulSoup(main, features="html5lib")
|
soup = BeautifulSoup(main.text, features="html5lib")
|
||||||
|
|
||||||
all_versions = list(map(
|
all_versions = list(map(
|
||||||
lambda option: option.attrs['value'],
|
lambda option: option.attrs['value'],
|
||||||
@@ -48,7 +49,7 @@ all_versions = list(map(
|
|||||||
latest_minor_versions = get_latest_minor_versions(all_versions)
|
latest_minor_versions = get_latest_minor_versions(all_versions)
|
||||||
latest_minor_versions_urls = [RELNOTES_URL_TEMPLATE.format(version=v) for v in latest_minor_versions]
|
latest_minor_versions_urls = [RELNOTES_URL_TEMPLATE.format(version=v) for v in latest_minor_versions]
|
||||||
|
|
||||||
for response in endoflife.fetch_urls(latest_minor_versions_urls):
|
for response in http.fetch_urls(latest_minor_versions_urls):
|
||||||
for (version, date_str) in re.findall(PATTERN, response.text, re.MULTILINE):
|
for (version, date_str) in re.findall(PATTERN, response.text, re.MULTILINE):
|
||||||
version = f"{version}.0" if len(version.split(".")) == 2 else version # convert x.y to x.y.0
|
version = f"{version}.0" if len(version.split(".")) == 2 else version # convert x.y to x.y.0
|
||||||
date = dates.parse_date(date_str).strftime("%Y-%m-%d")
|
date = dates.parse_date(date_str).strftime("%Y-%m-%d")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
PRODUCT = "typo3"
|
PRODUCT = "typo3"
|
||||||
@@ -7,8 +8,8 @@ URL = "https://get.typo3.org/api/v1/release/"
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
data = json.loads(response)
|
data = json.loads(response.text)
|
||||||
for v in data:
|
for v in data:
|
||||||
if v['type'] != 'development':
|
if v['type'] != 'development':
|
||||||
date = v["date"][0:10]
|
date = v["date"][0:10]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
# Fetches the Unity LTS releases from the Unity website. Non-LTS releases are not listed there,
|
# Fetches the Unity LTS releases from the Unity website. Non-LTS releases are not listed there,
|
||||||
@@ -13,8 +14,8 @@ URL = 'https://unity.com/releases/editor/qa/lts-releases'
|
|||||||
|
|
||||||
def fetch_releases(releases, url) -> str:
|
def fetch_releases(releases, url) -> str:
|
||||||
print(url)
|
print(url)
|
||||||
response = endoflife.fetch_url(url)
|
response = http.fetch_url(url)
|
||||||
soup = BeautifulSoup(response, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
|
|
||||||
for release in soup.find_all('div', class_='component-releases-item__show__inner-header'):
|
for release in soup.find_all('div', class_='component-releases-item__show__inner-header'):
|
||||||
version = release.find('h4').find('span').text
|
version = release.find('h4').find('span').text
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import mwparserfromhell
|
import mwparserfromhell
|
||||||
import re
|
import re
|
||||||
|
from common import http
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
URL = "https://www.unrealircd.org/docwiki/index.php?title=History_of_UnrealIRCd_releases&action=raw"
|
URL = "https://www.unrealircd.org/docwiki/index.php?title=History_of_UnrealIRCd_releases&action=raw"
|
||||||
|
|
||||||
print("::group::unrealircd")
|
print("::group::unrealircd")
|
||||||
response = endoflife.fetch_url(URL)
|
response = http.fetch_url(URL)
|
||||||
wikicode = mwparserfromhell.parse(response)
|
wikicode = mwparserfromhell.parse(response.text)
|
||||||
|
|
||||||
versions = {}
|
versions = {}
|
||||||
for tr in wikicode.ifilter_tags(matches=lambda node: node.tag == "tr"):
|
for tr in wikicode.ifilter_tags(matches=lambda node: node.tag == "tr"):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from common import http
|
||||||
from common import dates
|
from common import dates
|
||||||
from common import endoflife
|
from common import endoflife
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ URLS = [
|
|||||||
print(f"::group::{PRODUCT}")
|
print(f"::group::{PRODUCT}")
|
||||||
versions = {}
|
versions = {}
|
||||||
|
|
||||||
for response in endoflife.fetch_urls(URLS):
|
for response in http.fetch_urls(URLS):
|
||||||
soup = BeautifulSoup(response.text, features="html5lib")
|
soup = BeautifulSoup(response.text, features="html5lib")
|
||||||
for table in soup.find_all("table"):
|
for table in soup.find_all("table"):
|
||||||
headers = [th.get_text().strip().lower() for th in table.find_all("th")]
|
headers = [th.get_text().strip().lower() for th in table.find_all("th")]
|
||||||
|
|||||||
Reference in New Issue
Block a user