CoolFace
Apppublic

gauravmeena0708/epfo-circulars

sourceHugging Faceupdated 26d agoView on Hugging Face
0likes
fetch.py195 linesDownload Raw Back to root
1from bs4 import BeautifulSoup as bs, NavigableString2import requests3from urllib.parse import urljoin4import json5from datetime import datetime6import os7import argparse # For command-line arguments8
9# --- Configuration ---
10# If tesseract is not in your PATH, you might need to specify its location
11# For example, on Windows:
12# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
13
14CIRCULAR_DATA_FILE = "circular-data.json"15
16HEADERS = {
17    'Host': 'www.epfindia.gov.in',
18    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
19    'Accept': 'text/html, */*; q=0.01',
20    'Accept-Language': 'en-US,en;q=0.5',
21    'Accept-Encoding': 'gzip, deflate, br',
22    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
23    'X-Requested-With': 'XMLHttpRequest',
24    'Referer': 'https://www.epfindia.gov.in/site_en/Contact_office_wise.php?id=MHEM',
25    'Connection': 'keep-alive',
26    'Upgrade-Insecure-Requests': '1'
27}
28
29def generate_year_params():
30    """Dynamically generates financial year parameters up to the current date."""
31    current_date = datetime.now()
32    current_year = current_date.year
33    current_month = current_date.month
34    
35    # Financial year in India starts in April
36    if current_month >= 4:
37        latest_start_year = current_year
38    else:
39        latest_start_year = current_year - 1
40        
41    params = []
42    # Oldest explicitly listed year before 'Old Circulars' is 2009-2010
43    for year in range(latest_start_year, 2008, -1):
44        params.append(f"yr={year}-{year+1}")
45        
46    params.append("yr=Old+Circulars")
47    return params
48
49YEAR_PARAMS = generate_year_params()
50
51# --- Utility Functions ---
52def load_json_file(filepath):
53    """Loads a JSON file if it exists, otherwise returns an empty dictionary."""
54    if os.path.exists(filepath):
55        try:
56            with open(filepath, 'r', encoding='utf-8') as f:
57                return json.load(f)
58        except json.JSONDecodeError:
59            print(f"Warning: Could not decode JSON from {filepath}. Starting fresh.")
60            return {}
61    return {}
62
63def save_json_file(data, filepath):
64    """Saves data to a JSON file."""
65    with open(filepath, 'w', encoding='utf-8') as f:
66        json.dump(data, f, ensure_ascii=False, indent=4)
67    print(f"Data saved to {filepath}")
68
69# --- Main Data Fetching Logic ---70def fetch_circular_metadata():
71    """Fetches circular metadata from EPFO website and saves to circular-data.json."""
72    parsed_circulars_data = []
73    print("Starting to fetch circular metadata...")
74
75    for q_param in YEAR_PARAMS:
76        current_page_url = f'https://www.epfindia.gov.in/site_en/get_cir_content.php?{q_param}'
77        print(f"  Requesting URL: {current_page_url}")
78
79        try:
80            r = requests.get(current_page_url, headers=HEADERS, timeout=20)
81            r.raise_for_status()
82            soup = bs(r.text, 'html.parser')
83            print(f"  Status Code: {r.status_code} for {q_param}")
84
85            table_rows = soup.find_all('tr')
86            data_rows = table_rows[1:] if table_rows and table_rows[0].find('th') else table_rows
87
88            for row_idx, row in enumerate(data_rows):
89                cells = row.find_all('td')
90                if len(cells) < 4:
91                    # print(f"    Skipping row {row_idx+1} in {q_param} due to insufficient cells ({len(cells)}).")
92                    continue
93
94                serial_no = cells[0].get_text(strip=True)
95                subject_cell = cells[1]
96                title_parts = [content.strip() for content in subject_cell.contents if isinstance(content, NavigableString) and content.strip()]
97                title = " ".join(title_parts).split('Circular No.')[0].split('No.')[0].strip() # Basic title cleaning
98
99                circular_no_date_raw = ""
100                after_first_br = False
101                temp_circular_parts = []
102                for content in subject_cell.contents:
103                    if content.name == 'br':
104                        if not after_first_br:
105                            after_first_br = True
106                            continue
107                        else: # Second br or end of relevant part
108                            break
109                    if after_first_br:
110                        if isinstance(content, NavigableString):
111                            text_content = content.strip()
112                            if text_content:
113                                temp_circular_parts.append(text_content)
114                        elif content.name == 'a' and temp_circular_parts: # Link after some text
115                            break
116                circular_no_date_raw = " ".join(filter(None, temp_circular_parts))
117
118
119                circular_no = ""
120                date_of_circular = ""
121                delimiter_dated = " dated "
122                delimiter_date = " date " # some entries use "date" instead of "dated"
123                
124                actual_delimiter = None
125                if delimiter_dated in circular_no_date_raw.lower(): # Check lower case
126                    actual_delimiter = delimiter_dated
127                elif delimiter_date in circular_no_date_raw.lower():
128                    actual_delimiter = delimiter_date
129
130                if actual_delimiter:
131                    # Find the actual delimiter with original casing for split
132                    delimiter_pos = circular_no_date_raw.lower().find(actual_delimiter)
133                    original_delimiter = circular_no_date_raw[delimiter_pos : delimiter_pos + len(actual_delimiter)]
134                    
135                    parts = circular_no_date_raw.split(original_delimiter, 1)
136                    circular_no = parts[0].strip()
137                    if len(parts) > 1:
138                        date_of_circular = parts[1].strip()
139                else:
140                    circular_no = circular_no_date_raw.strip()
141                
142                # Further clean title from circular number if any residue
143                if circular_no and title.endswith(circular_no): # simple check
144                    title = title[:-len(circular_no)].strip()
145
146
147                def get_pdf_link(cell, base_url):
148                    link_tag = cell.find('a')
149                    if link_tag and link_tag.has_attr('href'):
150                        relative_link = link_tag['href']
151                        return urljoin(base_url, relative_link)
152                    return None
153
154                hindi_pdf_link = get_pdf_link(cells[2], current_page_url)
155                english_pdf_link = get_pdf_link(cells[3], current_page_url)
156
157                circular_data = {
158                    "serial_no": serial_no,
159                    "title": title,
160                    "circular_no": circular_no,
161                    "date": date_of_circular,
162                    "hindi_pdf_link": hindi_pdf_link,
163                    "english_pdf_link": english_pdf_link
164                }
165                parsed_circulars_data.append(circular_data)
166            print(f"  Successfully processed {q_param}")
167
168        except requests.exceptions.RequestException as e:
169            print(f"  Error fetching {q_param}: {e}")
170        except Exception as e:
171            print(f"  An error occurred during parsing for {q_param}: {e}")
172
173    if parsed_circulars_data:
174        save_json_file(parsed_circulars_data, CIRCULAR_DATA_FILE)
175    else:
176        print("No circular metadata was extracted.")
177    print("Finished fetching circular metadata.")
178
179
180# --- Main Execution ---
181if __name__ == "__main__":182    parser = argparse.ArgumentParser(description="Fetch EPFO circular metadata.")183    parser.add_argument(184        "--action",185        choices=['fetch', 'all'],186        default='fetch',187        help="'fetch' updates circular-data.json. 'all' is kept as a backwards-compatible alias."188    )189    args = parser.parse_args()190 191    if args.action in {'fetch', 'all'}:192        fetch_circular_metadata()193
194    print("\nScript finished.")
195