warn-scraper 1.2.167.dev0__py3-none-any.whl → 1.2.169.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
warn/scrapers/nd.py ADDED
@@ -0,0 +1,96 @@
1
+ import logging
2
+ import re
3
+ from pathlib import Path
4
+
5
+ import pdfplumber
6
+
7
+ from .. import utils
8
+ from ..cache import Cache
9
+
10
+ __authors__ = ["riordan"]
11
+ __tags__ = ["pdf"]
12
+ __source__ = {
13
+ "name": "Job Service North Dakota",
14
+ "url": "https://www.jobsnd.com/documents",
15
+ }
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def scrape(
21
+ data_dir: Path = utils.WARN_DATA_DIR,
22
+ cache_dir: Path = utils.WARN_CACHE_DIR,
23
+ ) -> Path:
24
+ """
25
+ Scrape data from North Dakota.
26
+
27
+ Keyword arguments:
28
+ data_dir -- the Path were the result will be saved (default WARN_DATA_DIR)
29
+ cache_dir -- the Path where results can be cached (default WARN_CACHE_DIR)
30
+
31
+ Returns: the Path where the file is written
32
+ """
33
+ # Fire up the cache
34
+ cache = Cache(cache_dir)
35
+ state_code = "nd"
36
+
37
+ # North Dakota publishes a single static PDF covering 2015 to the present.
38
+ pdf_url = "https://www.jobsnd.com/sites/www/files/documents/jsnd-documents/WARN%20Notices%202015%20to%20present.pdf"
39
+
40
+ # Download the PDF and stash the raw file, unedited, in the cache
41
+ cache_key = f"{state_code}/WARN_Notices_2015_to_present.pdf"
42
+ pdf_path = cache.download(cache_key, pdf_url)
43
+
44
+ # Loop through the PDF pages and pull out the table
45
+ output_rows: list = []
46
+ header_written = False
47
+ with pdfplumber.open(pdf_path) as pdf:
48
+ for page_index, page in enumerate(pdf.pages):
49
+ rows = page.extract_table() or []
50
+
51
+ for row in rows:
52
+ # Standardize each cell
53
+ output_row = [_clean_text(cell) for cell in row]
54
+
55
+ # Skip fully empty rows
56
+ if not any(output_row):
57
+ continue
58
+
59
+ # The header repeats at the top of the table. Keep it once.
60
+ if output_row[0] == "Company Name":
61
+ if header_written:
62
+ logger.debug(
63
+ f"Skipping repeated header row on page {page_index + 1}"
64
+ )
65
+ continue
66
+ header_written = True
67
+
68
+ output_rows.append(output_row)
69
+
70
+ # Write out to CSV
71
+ data_path = data_dir / f"{state_code}.csv"
72
+ utils.write_rows_to_csv(data_path, output_rows)
73
+
74
+ # Return the path
75
+ return data_path
76
+
77
+
78
+ def _clean_text(text: str) -> str:
79
+ """
80
+ Clean up text from a PDF cell.
81
+
82
+ Keyword arguments:
83
+ text -- the text to clean
84
+
85
+ Returns: the cleaned text
86
+ """
87
+ # Replace None with an empty string
88
+ if text is None:
89
+ return ""
90
+
91
+ # Collapse newlines and standardize whitespace
92
+ return re.sub(r"\s+", " ", text).strip()
93
+
94
+
95
+ if __name__ == "__main__":
96
+ scrape()
warn/scrapers/tx.py CHANGED
@@ -44,6 +44,7 @@ def scrape(
44
44
  session = requests.Session()
45
45
 
46
46
  page = session.get(url)
47
+ logger.debug(f"Page retrieval with response code {page.status_code}")
47
48
 
48
49
  # page = utils.get_url(
49
50
  # url,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warn-scraper
3
- Version: 1.2.167.dev0
3
+ Version: 1.2.169.dev0
4
4
  Summary: Command-line interface for downloading WARN Act notices of qualified plant closings and mass layoffs from state government websites
5
5
  Author-email: Big Local News <biglocalnews@stanford.edu>
6
6
  License-Expression: Apache-2.0
@@ -36,6 +36,7 @@ warn/scrapers/mi.py,sha256=Ppyawp4nbzSBODuzDKeqnO9_9do5MFwK4Y_f3uc6blE,5846
36
36
  warn/scrapers/mo.py,sha256=wnnwQAiVPwuheMqptMXZpyQdiKNghhKwTO-Bnh9oXoU,3492
37
37
  warn/scrapers/ms.py,sha256=BZZoMw3TNtwzBeBsqKLsPznBcDjPaO49I5-yBU0e9AI,5502
38
38
  warn/scrapers/mt.py,sha256=t2MP4OCcuCEnrnvNgOu289P0eekZq4XaCK65qzgZX88,2457
39
+ warn/scrapers/nd.py,sha256=x-mLrysn-Ns3tXg6MmjUYZ-4T0x6YUix_fuIldO5Dk0,2657
39
40
  warn/scrapers/ne.py,sha256=JawuGJ3tCKvMd-N-p03gnltB4rol4QUJshMk2oyMPO4,4143
40
41
  warn/scrapers/nj.py,sha256=nwbMbeQuUJbYRVoyUyKZBmNqvqsXu3Habt-10r8DvZE,2230
41
42
  warn/scrapers/nm.py,sha256=55kDh65uOc-uMO8wuxPHhHN4Aw8Di2Fx66JBds8f2Io,3694
@@ -48,17 +49,17 @@ warn/scrapers/ri.py,sha256=EUyLy59eNiYHqiJR8C0YcJrZtp09KyVc45AFD0_Uc0U,4497
48
49
  warn/scrapers/sc.py,sha256=p3kscSNSW9C8C5QaSUbCAo6XibgB7G2iH6zaMH7Mnsc,4819
49
50
  warn/scrapers/sd.py,sha256=_4R19Ybzsyx1PvcWV3_laJmJ3etrwVGfhNEQm6njwoA,1904
50
51
  warn/scrapers/tn.py,sha256=i1H7c09Ea3CDrTXqqRMLBMPT_34QtGA0-x7T8rm_j5Q,2945
51
- warn/scrapers/tx.py,sha256=y_pRNuTzaQBkHpRKJXA5Fw8ZsjyPJIVElcwGinlf9wk,5594
52
+ warn/scrapers/tx.py,sha256=b9CWn9erFbYFvTacHUy7x-2YfjSuf_VGyt2bUlwq0c8,5668
52
53
  warn/scrapers/ut.py,sha256=iUh38YIjbvv5MyyKacsiZNe8KjfdBeDaOf-qMQEF_kc,2245
53
54
  warn/scrapers/va.py,sha256=7Nle7qL0VNPiE653XyaP9HQqSfuJFDRr2kEkjOqLvFM,11269
54
55
  warn/scrapers/vt.py,sha256=d-bo4WK2hkrk4BhCCmLpEovcoZltlvdIUB6O0uaMx5A,1186
55
56
  warn/scrapers/wa.py,sha256=UXdVtHZo_a-XfoiyOooTRfTb9W3PErSZdKca6SRORgs,4282
56
57
  warn/scrapers/wi.py,sha256=ClEzXkwZbop0W4fkQgsb5oHAPUrb4luUPGV-jOKwkcg,4855
57
- warn_scraper-1.2.167.dev0.dist-info/licenses/LICENSE,sha256=ZV-QHyqPwyMuwuj0lI05JeSjV1NyzVEk8Yeu7FPtYS0,585
58
- warn_scraper-1.2.167.dev0.dist-info/METADATA,sha256=o8cXc8EYMBQsINkZuOBP3Htq8b0ivMlcghMBvDULdsM,1780
59
- warn_scraper-1.2.167.dev0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
60
- warn_scraper-1.2.167.dev0.dist-info/entry_points.txt,sha256=poh_oSweObGlBSs1_2qZmnTodlOYD0KfO7-h7W2UQIw,47
61
- warn_scraper-1.2.167.dev0.dist-info/scm_file_list.json,sha256=SBROd7oEfcspIMl6v7yfGzQd2rkWxybkiIXopuTQVL0,4120
62
- warn_scraper-1.2.167.dev0.dist-info/scm_version.json,sha256=nJW_1cgs89YVm56svBNN1TWCOn0itYdI0qFA45a9Guw,161
63
- warn_scraper-1.2.167.dev0.dist-info/top_level.txt,sha256=dZfms6N3kqVXufiPOo7YqOrAcUtYfNH_oyGvYUk9FB4,5
64
- warn_scraper-1.2.167.dev0.dist-info/RECORD,,
58
+ warn_scraper-1.2.169.dev0.dist-info/licenses/LICENSE,sha256=ZV-QHyqPwyMuwuj0lI05JeSjV1NyzVEk8Yeu7FPtYS0,585
59
+ warn_scraper-1.2.169.dev0.dist-info/METADATA,sha256=idfAf3i-wQj6UbjYHXsG73B3pU8lvD4soFhnBis-e5c,1780
60
+ warn_scraper-1.2.169.dev0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
61
+ warn_scraper-1.2.169.dev0.dist-info/entry_points.txt,sha256=poh_oSweObGlBSs1_2qZmnTodlOYD0KfO7-h7W2UQIw,47
62
+ warn_scraper-1.2.169.dev0.dist-info/scm_file_list.json,sha256=PsUNADDLfiY6WCIaKYrGF-hSOzV6HMBxtHeJPnHAOAo,4147
63
+ warn_scraper-1.2.169.dev0.dist-info/scm_version.json,sha256=4YY6UpShLyl1mHlnjJR2Fi1BirY30YZrm1c8Wg1OkHA,161
64
+ warn_scraper-1.2.169.dev0.dist-info/top_level.txt,sha256=dZfms6N3kqVXufiPOo7YqOrAcUtYfNH_oyGvYUk9FB4,5
65
+ warn_scraper-1.2.169.dev0.dist-info/RECORD,,
@@ -77,6 +77,7 @@
77
77
  "warn/scrapers/al.py",
78
78
  "warn/scrapers/fl.py",
79
79
  "warn/scrapers/nj.py",
80
+ "warn/scrapers/nd.py",
80
81
  "warn/scrapers/mi.py",
81
82
  "warn/scrapers/vt.py",
82
83
  "warn/scrapers/mt.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.2.168",
3
+ "distance": 0,
4
+ "node": "gf7b3dd26af1f3ad700762504bd7c5c0d23979507",
5
+ "dirty": true,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-14"
8
+ }
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.2.166",
3
- "distance": 0,
4
- "node": "g78c3c39e09c4afbd179c2da73190054fe97deedc",
5
- "dirty": true,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-09"
8
- }