diff --git a/.github/workflows/run_scraper.yml b/.github/workflows/run_scraper.yml index ec8a8d5..5223855 100644 --- a/.github/workflows/run_scraper.yml +++ b/.github/workflows/run_scraper.yml @@ -6,6 +6,9 @@ on: - cron: '0 */12 * * *' workflow_dispatch: # Cho phép bạn bấm nút "Run" thủ công trên GitHub +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-latest diff --git a/crawl/TopDevJob.py b/crawl/TopDevJob.py index fd354d9..77707d5 100644 --- a/crawl/TopDevJob.py +++ b/crawl/TopDevJob.py @@ -9,6 +9,7 @@ def __init__(self, page, webhook_url): super().__init__(page = page, webhook_url = webhook_url) self.url = "https://topdev.vn/jobs/search" self.find_level = ["Intern", "Fresher", "Junior"] + self.scraped_links = set() # Dùng để lưu các link đã cào được, tránh trùng lặp khi crawl nhiều trang async def crawl(self): jobs = [] container = self.page.locator("div.flex-col.gap-2").first @@ -18,8 +19,10 @@ async def crawl(self): # print(f"📄 Processing card {i+1}/{len(cards)}...") card = cards[i] job_data = await self.parse_card_detail(card) # Hàm bóc tách chi tiết đã viết - if(job_data.exp in self.find_level and job_data.address.find("Hồ Chí Minh") != -1): # Hồ Chí Minh Hà Nội - jobs.append(job_data) + if(job_data.exp in self.find_level and job_data.address.find("Hồ Chí Minh") != -1 ): # Hồ Chí Minh Hà Nội + if job_data.link not in self.scraped_links: + jobs.append(job_data) + self.scraped_links.add(job_data.link) # Ghi chú lại link này return jobs async def parse_card_detail(self, card): @@ -78,22 +81,23 @@ async def crawl_all_pages(self, today = False): # Kiểm tra nếu nút Next còn tồn tại và có thể click được # (Nút Next cuối cùng thường có class opacity-0 hoặc hidden) - if await next_button.count() > 0: + if await next_button.count() > 0 and await next_button.is_visible() and await next_button.is_enabled(): + # Lấy class để kiểm tra xem có bị ẩn (trang cuối) không class_attr = await next_button.get_attribute("class") + if "pointer-events-none opacity-0" in class_attr: + print("🚫 Next button is present but disabled (opacity-0 or pointer-events-none). This is the last page!") + return all_jobs # Nếu KHÔNG chứa 'opacity-0' thì mới là nút bấm được - if "opacity-0" not in class_attr: - print("➡️ Nút Next đang sẵn sàng, bấm để sang trang tiếp...") - await next_button.click() - current_page += 1 - # Đợi dữ liệu mới nạp xong - await self.page.wait_for_load_state("networkidle") - # Đợi thêm 1s để chắc chắn các card cũ đã bị thay thế (tránh cào trùng) - await self.page.wait_for_timeout(1000) - else: - print("🏁 Đã thấy nút Next nhưng nó bị ẩn (Trang cuối rồi).") - return all_jobs + print("➡️ Next button is visible and enabled. Clicking to go to the next page...") + await next_button.click(force=True) # Dùng force để đảm bảo click dù có phần tử nào đó chồng lên + current_page += 1 + # Đợi dữ liệu mới nạp xong + await self.page.wait_for_load_state("networkidle") + # Đợi thêm 1s để chắc chắn các card cũ đã bị thay thế (tránh cào trùng) + await self.page.wait_for_timeout(1000) + async def crawl_today(self): jobs = [] @@ -105,7 +109,9 @@ async def crawl_today(self): card = cards[i] job_data = await self.parse_card_detail(card) # Hàm bóc tách chi tiết đã viết if(job_data.exp in self.find_level and job_data.address.find("Hồ Chí Minh") != -1 and job_data.posted_date.find("hours") != -1): # Hồ Chí Minh Hà Nội - jobs.append(job_data) + if job_data.link not in self.scraped_links: + jobs.append(job_data) + self.scraped_links.add(job_data.link) # Ghi chú lại link này return jobs def send_to_discord(self, job_data): diff --git a/main.py b/main.py index b8a95c6..c1c5804 100644 --- a/main.py +++ b/main.py @@ -24,6 +24,7 @@ async def test_scraper(): # 4. Gọi hàm crawl jobs = await scraper.crawl_all_pages(today=True) # Nếu bạn chỉ muốn crawl hôm nay thì truyền today=True) print(f"✅ Crawled {len(jobs)} jobs from TopDev.vn") + scraper.print_jobs(jobs) # In ra console để kiểm tra # 5. In kết quả ra màn hình để kiểm tra for job in jobs: scraper.send_to_discord(job) diff --git a/models/Job.py b/models/Job.py index 55dc3b7..11d62c1 100644 --- a/models/Job.py +++ b/models/Job.py @@ -21,4 +21,5 @@ def to_discord_embed(self): "color": 5814783, # Màu xanh dương "footer": {"text": f"Nguồn: {self.posted_date}"}, "image": {"url": self.image} - } \ No newline at end of file + } + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index caf01c7..7e868a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,6 @@ MarkupSafe==3.0.3 mashumaro==3.14 more-itertools==10.8.0 msgpack==1.1.2 -networkx==3.6.1 orderly-set==5.5.0 packaging==26.0 parsedatetime==2.6