abinazebinoy commited on
Commit
8af87f7
·
1 Parent(s): f39bc7e

fix( ): add 5 scrapers, fix README links

Browse files

README FIX:
- Converted footer links to clickable markdown format
- Added PHASE_ROADMAP.md link

SCRAPERS RESTORED:
- scrapers/loksabha_scraper.py
- scrapers/sebi_scraper.py
- scrapers/electoral_bond_scraper.py
- scrapers/opensanctions_scraper.py
- scrapers/wikidata_scraper.py

.env.example CHANGED
@@ -1,17 +1,22 @@
1
- # Copy this file to .env and fill in your values
2
- # NEVER commit .env to GitHub
 
 
3
 
4
- NEO4J_URI=neo4j+s://1ahvhg668.databases.neo4j.io
5
- NEO4J_USER=1yuy33b8
6
- NEO4J_PASSWORD=t3TxyBvDeGDXyuytyjbjjkCD8P6dwAj-XlHgVBBH4SOUxcdNcCrHI
7
 
8
- DATAGOV_API_KEY=579b464db66ec23gjhhg09975e9fb96f49ghfhjkvxc4bebd8f320
9
- OPENAI_API_KEY=your_openai_key_here
 
 
10
 
11
- # Optional: News API (free at https://newsapi.org)
12
- NEWSAPI_KEY=your_newsapi_key_here
13
-
14
- # OpenSanctions API (free at opensanctions.org)
15
- OPENSANCTIONS_API_KEY=d86bbf9c96a76465cvclkjhgfd4f048860282
16
 
 
 
 
17
 
 
 
 
1
+ # BharatGraph Environment Variables
2
+ # Copy this file to .env and add your REAL values there
3
+ # This file uses placeholders only — NEVER put real keys here
4
+ # .env is git-ignored and stays only on your computer
5
 
6
+ # data.gov.in — register free at https://data.gov.in/user/register
7
+ DATAGOV_API_KEY=your_datagov_api_key_here
 
8
 
9
+ # Neo4j AuraDB — free at https://neo4j.com/cloud/platform/aura-graph-database
10
+ NEO4J_URI=neo4j+s://your-instance.databases.neo4j.io
11
+ NEO4J_USER=neo4j
12
+ NEO4J_PASSWORD=your_generated_password_here
13
 
14
+ # OpenSanctions — free API key at https://opensanctions.org/api
15
+ OPENSANCTIONS_API_KEY=your_opensanctions_api_key_here
 
 
 
16
 
17
+ # Copernicus Satellite — free at https://dataspace.copernicus.eu
18
+ COPERNICUS_USER=your_copernicus_username_here
19
+ COPERNICUS_PASSWORD=your_copernicus_password_here
20
 
21
+ # News API — optional, free at https://newsapi.org
22
+ NEWSAPI_KEY=your_newsapi_key_here
README.md CHANGED
@@ -303,14 +303,9 @@ Pipeline 47 records processed and saved in 15 seconds
303
 
304
  ---
305
 
306
- ## License
307
 
308
- MIT. See `LICENSE`.
309
-
310
- ## Contributing
311
-
312
- See `CONTRIBUTING.md`.
313
-
314
- ## Security
315
-
316
- See `SECURITY.md`.
 
303
 
304
  ---
305
 
306
+ ## Reference Documents
307
 
308
+ - [Contributing Guidelines](CONTRIBUTING.md)
309
+ - [Security Policy](SECURITY.md)
310
+ - [Phase Roadmap](PHASE_ROADMAP.md)
311
+ - [MIT License](LICENSE)
 
 
 
 
 
scrapers/electoral_bond_scraper.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from bs4 import BeautifulSoup
4
+ from scrapers.base_scraper import BaseScraper
5
+ from loguru import logger
6
+
7
+
8
+ class ElectoralBondScraper(BaseScraper):
9
+
10
+ ECI_URL = "https://www.eci.gov.in/electoral-bonds"
11
+
12
+ def __init__(self):
13
+ super().__init__(name="electoral_bond", delay=2.0)
14
+
15
+ def fetch_bond_data(self, save: bool = True) -> list:
16
+ logger.info("[ElectoralBond] Fetching bond disclosure data...")
17
+ html = self.get_html(self.ECI_URL)
18
+ bonds = []
19
+ if html:
20
+ bonds = self._parse_bond_page(html)
21
+ if not bonds:
22
+ logger.warning("[ElectoralBond] Live page returned no data — using sample")
23
+ bonds = self._get_sample_bonds()
24
+ if save and bonds:
25
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
26
+ filepath = f"data/samples/electoral_bonds_{timestamp}.json"
27
+ self.save_json(bonds, filepath)
28
+ logger.success(f"[ElectoralBond] Saved {len(bonds)} records")
29
+ return bonds
30
+
31
+ def _parse_bond_page(self, html: str) -> list:
32
+ soup = BeautifulSoup(html, "lxml")
33
+ bonds = []
34
+ for link in soup.find_all("a", href=True):
35
+ title = link.get_text(strip=True)
36
+ href = link.get("href", "")
37
+ if any(kw in title.lower() for kw in ["bond","donor","purchase","redemption"]):
38
+ bonds.append({
39
+ "title": title,
40
+ "url": href if href.startswith("http") else self.ECI_URL,
41
+ "source": "Election Commission of India",
42
+ "source_url": self.ECI_URL,
43
+ "scraped_at": datetime.now().isoformat(),
44
+ "entity_type": "electoral_bond_disclosure",
45
+ })
46
+ return bonds
47
+
48
+ def _get_sample_bonds(self) -> list:
49
+ return [
50
+ {
51
+ "bond_number": "EB001",
52
+ "purchaser_name": "SAMPLE CORPORATION LTD",
53
+ "denomination_crore": 1.0,
54
+ "purchase_date": "2023-04-05",
55
+ "redemption_date": "2023-04-12",
56
+ "redeemed_by": "SAMPLE POLITICAL PARTY",
57
+ "source": "Supreme Court ordered ECI disclosure (sample)",
58
+ "source_url": self.ECI_URL,
59
+ "scraped_at": datetime.now().isoformat(),
60
+ "entity_type": "electoral_bond",
61
+ },
62
+ ]
63
+
64
+
65
+ if __name__ == "__main__":
66
+ print("=" * 55)
67
+ print("BharatGraph - Electoral Bond Scraper")
68
+ print("=" * 55)
69
+ scraper = ElectoralBondScraper()
70
+ bonds = scraper.fetch_bond_data(save=True)
71
+ print(f"\n Total records: {len(bonds)}")
72
+ print("\nDone!")
scrapers/loksabha_scraper.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from bs4 import BeautifulSoup
4
+ from scrapers.base_scraper import BaseScraper
5
+ from loguru import logger
6
+
7
+
8
+ class LokSabhaScraper(BaseScraper):
9
+
10
+ BASE_URL = "https://loksabha.nic.in"
11
+ QUESTIONS_URL = "https://loksabha.nic.in/Questions/QResult15.aspx"
12
+
13
+ MINISTRIES = [
14
+ "Ministry of Finance",
15
+ "Ministry of Home Affairs",
16
+ "Ministry of Rural Development",
17
+ "Ministry of Health",
18
+ ]
19
+
20
+ def __init__(self):
21
+ super().__init__(name="loksabha", delay=3.0)
22
+ self.session.headers.update({
23
+ "User-Agent": (
24
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
25
+ "AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
26
+ ),
27
+ })
28
+
29
+ def fetch_questions(self, ministry: str = "Ministry of Finance",
30
+ limit: int = 20) -> list:
31
+ logger.info(f"[LokSabha] Fetching questions for: {ministry}")
32
+ html = self.get_html(self.QUESTIONS_URL, params={"ministry": ministry})
33
+ if not html:
34
+ logger.warning("[LokSabha] Could not fetch page — using sample")
35
+ return self._get_sample_questions(ministry)
36
+ questions = self._parse_questions(html, ministry, limit)
37
+ if not questions:
38
+ return self._get_sample_questions(ministry)
39
+ return questions
40
+
41
+ def _parse_questions(self, html: str, ministry: str, limit: int) -> list:
42
+ soup = BeautifulSoup(html, "lxml")
43
+ questions = []
44
+ for row in soup.find_all("tr")[1:limit + 1]:
45
+ cols = row.find_all("td")
46
+ if len(cols) < 3:
47
+ continue
48
+ question = {
49
+ "question_number": cols[0].get_text(strip=True),
50
+ "subject": cols[1].get_text(strip=True),
51
+ "member_name": cols[2].get_text(strip=True) if len(cols) > 2 else "",
52
+ "ministry": ministry,
53
+ "session": "17th Lok Sabha",
54
+ "source": "Lok Sabha",
55
+ "source_url": self.BASE_URL,
56
+ "scraped_at": datetime.now().isoformat(),
57
+ "entity_type": "parliamentary_question",
58
+ }
59
+ if question["subject"]:
60
+ questions.append(question)
61
+ logger.info(f"[LokSabha] Parsed {len(questions)} questions")
62
+ return questions
63
+
64
+ def _get_sample_questions(self, ministry: str) -> list:
65
+ return [
66
+ {
67
+ "question_number": "Q001",
68
+ "subject": "Status of PM-KISAN scheme beneficiary verification",
69
+ "member_name": "SAMPLE MP",
70
+ "ministry": ministry,
71
+ "session": "17th Lok Sabha",
72
+ "source": "Lok Sabha (sample)",
73
+ "source_url": self.BASE_URL,
74
+ "scraped_at": datetime.now().isoformat(),
75
+ "entity_type": "parliamentary_question",
76
+ },
77
+ {
78
+ "question_number": "Q002",
79
+ "subject": "Irregularities in MGNREGA fund disbursement",
80
+ "member_name": "SAMPLE MP",
81
+ "ministry": ministry,
82
+ "session": "17th Lok Sabha",
83
+ "source": "Lok Sabha (sample)",
84
+ "source_url": self.BASE_URL,
85
+ "scraped_at": datetime.now().isoformat(),
86
+ "entity_type": "parliamentary_question",
87
+ },
88
+ ]
89
+
90
+ def fetch_all_ministries(self, save: bool = True) -> list:
91
+ all_questions = []
92
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
93
+ for ministry in self.MINISTRIES:
94
+ questions = self.fetch_questions(ministry, limit=10)
95
+ all_questions.extend(questions)
96
+ if save and all_questions:
97
+ filepath = f"data/samples/loksabha_questions_{timestamp}.json"
98
+ self.save_json(all_questions, filepath)
99
+ logger.success(f"[LokSabha] Saved {len(all_questions)} questions")
100
+ return all_questions
101
+
102
+
103
+ if __name__ == "__main__":
104
+ print("=" * 55)
105
+ print("BharatGraph - Lok Sabha Scraper")
106
+ print("=" * 55)
107
+ scraper = LokSabhaScraper()
108
+ questions = scraper.fetch_all_ministries(save=True)
109
+ print(f"\n Total: {len(questions)}")
110
+ if questions:
111
+ print(f" Example: {questions[0]['subject'][:60]}")
112
+ print("\nDone!")
scrapers/opensanctions_scraper.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from scrapers.base_scraper import BaseScraper
4
+ from loguru import logger
5
+
6
+
7
+ class OpenSanctionsScraper(BaseScraper):
8
+
9
+ API_URL = "https://api.opensanctions.org/search/default"
10
+
11
+ def __init__(self):
12
+ super().__init__(name="opensanctions", delay=1.5)
13
+ self.api_key = os.getenv("OPENSANCTIONS_API_KEY", "")
14
+ if self.api_key:
15
+ self.session.headers.update({"Authorization": f"ApiKey {self.api_key}"})
16
+ else:
17
+ logger.warning("[OpenSanctions] No API key — add OPENSANCTIONS_API_KEY to .env")
18
+
19
+ def search_entity(self, name: str, schema: str = "Person") -> list:
20
+ logger.info(f"[OpenSanctions] Searching: '{name}'")
21
+ params = {"q": name, "schema": schema, "limit": 10}
22
+ data = self.get_json(self.API_URL, params=params)
23
+ if not data or "results" not in data:
24
+ return []
25
+ results = []
26
+ for r in data["results"]:
27
+ props = r.get("properties", {})
28
+ results.append({
29
+ "entity_id": r.get("id", ""),
30
+ "name": r.get("caption", name),
31
+ "schema": r.get("schema", schema),
32
+ "datasets": r.get("datasets", []),
33
+ "is_pep": "pep" in str(r.get("datasets", [])).lower(),
34
+ "is_sanctioned":any(d in ["us_ofac_sdn","un_sc_sanctions","eu_fsf"]
35
+ for d in r.get("datasets", [])),
36
+ "countries": props.get("country", []),
37
+ "positions": props.get("position", []),
38
+ "source": "OpenSanctions",
39
+ "source_url": "https://opensanctions.org",
40
+ "scraped_at": datetime.now().isoformat(),
41
+ "entity_type": "sanctions_check",
42
+ })
43
+ logger.success(f"[OpenSanctions] Found {len(results)} matches for '{name}'")
44
+ return results
45
+
46
+ def screen_entity_list(self, names: list) -> dict:
47
+ results = {}
48
+ for name in names:
49
+ matches = self.search_entity(name)
50
+ if any(m["is_sanctioned"] or m["is_pep"] for m in matches):
51
+ results[name] = matches
52
+ logger.warning(f"[OpenSanctions] FLAG: '{name}' in sanctions/PEP data")
53
+ else:
54
+ logger.info(f"[OpenSanctions] CLEAR: '{name}'")
55
+ return results
56
+
57
+ def fetch_and_save(self, names: list, save: bool = True) -> dict:
58
+ results = self.screen_entity_list(names)
59
+ if save and results:
60
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
61
+ self.save_json(results, f"data/samples/opensanctions_{timestamp}.json")
62
+ return results
63
+
64
+
65
+ if __name__ == "__main__":
66
+ print("=" * 55)
67
+ print("BharatGraph - OpenSanctions Scraper")
68
+ print("=" * 55)
69
+ scraper = OpenSanctionsScraper()
70
+ results = scraper.fetch_and_save(["Sample Person", "Test Corporation"], save=True)
71
+ print(f"\n Flagged: {len(results)}")
72
+ print("\nDone!")
scrapers/sebi_scraper.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from bs4 import BeautifulSoup
4
+ from scrapers.base_scraper import BaseScraper
5
+ from loguru import logger
6
+
7
+
8
+ class SEBIScraper(BaseScraper):
9
+
10
+ BASE_URL = "https://www.sebi.gov.in"
11
+ ORDERS_URL = "https://www.sebi.gov.in/enforcement/orders"
12
+
13
+ def __init__(self):
14
+ super().__init__(name="sebi", delay=2.0)
15
+ self.session.headers.update({
16
+ "User-Agent": (
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
18
+ "AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
19
+ ),
20
+ })
21
+
22
+ def fetch_enforcement_orders(self, limit: int = 20) -> list:
23
+ logger.info("[SEBI] Fetching enforcement orders...")
24
+ html = self.get_html(self.ORDERS_URL)
25
+ if not html:
26
+ logger.warning("[SEBI] Could not fetch page — using sample")
27
+ return self._get_sample_orders()
28
+ orders = self._parse_orders(html, limit)
29
+ if not orders:
30
+ return self._get_sample_orders()
31
+ return orders
32
+
33
+ def _parse_orders(self, html: str, limit: int) -> list:
34
+ soup = BeautifulSoup(html, "lxml")
35
+ orders = []
36
+ for link in soup.find_all("a", href=True):
37
+ href = link.get("href", "")
38
+ title = link.get_text(strip=True)
39
+ if not title or len(title) < 10:
40
+ continue
41
+ if not any(kw in href.lower() for kw in ["order","adjudication","settlement"]):
42
+ continue
43
+ full_url = href if href.startswith("http") else self.BASE_URL + href
44
+ orders.append({
45
+ "title": title,
46
+ "url": full_url,
47
+ "order_type": "Enforcement Order",
48
+ "source": "SEBI",
49
+ "source_url": self.BASE_URL,
50
+ "scraped_at": datetime.now().isoformat(),
51
+ "entity_type": "regulatory_order",
52
+ })
53
+ if len(orders) >= limit:
54
+ break
55
+ logger.info(f"[SEBI] Parsed {len(orders)} orders")
56
+ return orders
57
+
58
+ def _get_sample_orders(self) -> list:
59
+ return [
60
+ {
61
+ "title": "Adjudication Order against Director for insider trading",
62
+ "url": "https://www.sebi.gov.in/enforcement/orders/sample-1",
63
+ "order_type": "Adjudication Order",
64
+ "violation": "Insider trading under SEBI (PIT) Regulations 2015",
65
+ "source": "SEBI (sample)",
66
+ "source_url": self.BASE_URL,
67
+ "scraped_at": datetime.now().isoformat(),
68
+ "entity_type": "regulatory_order",
69
+ },
70
+ ]
71
+
72
+ def fetch_and_save(self, save: bool = True) -> list:
73
+ orders = self.fetch_enforcement_orders(limit=30)
74
+ if save and orders:
75
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
76
+ filepath = f"data/samples/sebi_orders_{timestamp}.json"
77
+ self.save_json(orders, filepath)
78
+ logger.success(f"[SEBI] Saved {len(orders)} orders")
79
+ return orders
80
+
81
+
82
+ if __name__ == "__main__":
83
+ print("=" * 55)
84
+ print("BharatGraph - SEBI Scraper")
85
+ print("=" * 55)
86
+ scraper = SEBIScraper()
87
+ orders = scraper.fetch_and_save(save=True)
88
+ print(f"\n Total orders: {len(orders)}")
89
+ if orders:
90
+ print(f" Example: {orders[0]['title'][:60]}")
91
+ print("\nDone!")
scrapers/wikidata_scraper.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from scrapers.base_scraper import BaseScraper
4
+ from loguru import logger
5
+
6
+
7
+ class WikidataScraper(BaseScraper):
8
+
9
+ SPARQL_URL = "https://query.wikidata.org/sparql"
10
+
11
+ def __init__(self):
12
+ super().__init__(name="wikidata", delay=1.0)
13
+ self.session.headers.update({
14
+ "Accept": "application/sparql-results+json",
15
+ "User-Agent": "BharatGraph/0.1 (civic-transparency-platform)",
16
+ })
17
+
18
+ def enrich_politician(self, name: str) -> dict:
19
+ logger.info(f"[Wikidata] Enriching: '{name}'")
20
+ query = f"""
21
+ SELECT ?person ?birthDate ?educationLabel ?positionLabel ?partyLabel WHERE {{
22
+ ?person wdt:P31 wd:Q5 .
23
+ ?person rdfs:label "{name}"@en .
24
+ OPTIONAL {{ ?person wdt:P569 ?birthDate }}
25
+ OPTIONAL {{ ?person wdt:P69 ?education }}
26
+ OPTIONAL {{ ?person wdt:P39 ?position }}
27
+ OPTIONAL {{ ?person wdt:P102 ?party }}
28
+ SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en" . }}
29
+ }}
30
+ LIMIT 5
31
+ """
32
+ data = self.get_json(self.SPARQL_URL, params={"query": query, "format": "json"})
33
+ bindings = data.get("results",{}).get("bindings",[]) if data else []
34
+ if not bindings:
35
+ return self._empty_enrichment(name)
36
+ return {
37
+ "name": name,
38
+ "wikidata_id":bindings[0].get("person",{}).get("value","").split("/")[-1],
39
+ "birth_date": bindings[0].get("birthDate",{}).get("value",""),
40
+ "education": list({b.get("educationLabel",{}).get("value","")
41
+ for b in bindings if b.get("educationLabel")}),
42
+ "positions": list({b.get("positionLabel",{}).get("value","")
43
+ for b in bindings if b.get("positionLabel")}),
44
+ "party": bindings[0].get("partyLabel",{}).get("value",""),
45
+ "source": "Wikidata",
46
+ "source_url": "https://www.wikidata.org",
47
+ "scraped_at": datetime.now().isoformat(),
48
+ }
49
+
50
+ def _empty_enrichment(self, name: str) -> dict:
51
+ return {"name": name, "wikidata_id": "", "birth_date": "",
52
+ "education": [], "positions": [], "party": "",
53
+ "source": "Wikidata", "source_url": "https://www.wikidata.org",
54
+ "scraped_at": datetime.now().isoformat()}
55
+
56
+ def enrich_entity_list(self, names: list, save: bool = True) -> list:
57
+ results = [self.enrich_politician(n) for n in names]
58
+ if save and results:
59
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
60
+ self.save_json(results, f"data/samples/wikidata_enrichment_{timestamp}.json")
61
+ logger.success(f"[Wikidata] Saved enrichment for {len(results)} entities")
62
+ return results
63
+
64
+
65
+ if __name__ == "__main__":
66
+ print("=" * 55)
67
+ print("BharatGraph - Wikidata Scraper")
68
+ print("=" * 55)
69
+ scraper = WikidataScraper()
70
+ results = scraper.enrich_entity_list(["Narendra Modi", "Rahul Gandhi"], save=True)
71
+ for r in results:
72
+ print(f"\n {r['name']}: ID={r['wikidata_id']} party={r['party']}")
73
+ print(f" positions={r['positions'][:2]}")
74
+ print("\nDone!")