Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/run_scraper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 21 additions & 15 deletions crawl/TopDevJob.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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 = []
Expand All @@ -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):
Expand Down
1 change: 1 addition & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion models/Job.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
}

1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading