From 0b1d451e2b464c21a248202436c14db8a4b8d9d1 Mon Sep 17 00:00:00 2001 From: ccppi Date: Thu, 13 Jun 2024 11:11:58 +0200 Subject: [PATCH] Initial commit --- .gitignore | 6 + db/Cantons.db | Bin 0 -> 188416 bytes example.txt | 1 + lib/conf | 55 ++++++++ lib/config.py | 56 ++++++++ lib/dateconverter.py | 14 ++ lib/db.py | 78 ++++++++++++ lib/gui.py | 297 +++++++++++++++++++++++++++++++++++++++++++ lib/helpers.py | 166 ++++++++++++++++++++++++ lib/login.py | 38 ++++++ lib/main.py | 6 + lib/scrap_jobs.py | 156 +++++++++++++++++++++++ lib/sysparse.py | 105 +++++++++++++++ querry.note | 4 + requirements.txt | 5 + 15 files changed, 987 insertions(+) create mode 100644 .gitignore create mode 100644 db/Cantons.db create mode 100644 example.txt create mode 100644 lib/conf create mode 100644 lib/config.py create mode 100644 lib/dateconverter.py create mode 100644 lib/db.py create mode 100644 lib/gui.py create mode 100644 lib/helpers.py create mode 100644 lib/login.py create mode 100644 lib/main.py create mode 100644 lib/scrap_jobs.py create mode 100644 lib/sysparse.py create mode 100644 querry.note create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b0f022 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.venv* +*.hide +output/ +*.db +*.csv +__pycache__ diff --git a/db/Cantons.db b/db/Cantons.db new file mode 100644 index 0000000000000000000000000000000000000000..f1cc1b04829347bd7498d84073d6bf4fe5e78ef6 GIT binary patch literal 188416 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�>{&VN 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 2010-02-01 + day="" + month="" + year="" + for i in range(0,1+1): + day+= date[i] + for i in range(3,4+1): + month+=date[i] + for i in range(6,9+1): + year+=date[i]; + newdate = year+"-"+month+"-"+day + return(newdate) + diff --git a/lib/db.py b/lib/db.py new file mode 100644 index 0000000..c419491 --- /dev/null +++ b/lib/db.py @@ -0,0 +1,78 @@ +import sqlite3 +import mmh3 +import sys +DEBUG = False + +def log(*s): + if DEBUG: + print(s) +def initdb(file): + with sqlite3.connect(file) as connection: + print("db connection", connection.total_changes) + cursor = connection.cursor() + cursor.execute("CREATE TABLE jobs (star TEXT,tag INT ,title TEXT, location TEXT, company TEXT,link TEXT,pubdate TEXT,hash INT)") + sys.exit() +def rmdb(file,table): + with sqlite3.connect(file) as connection: + question = input("Do you really wont to empty the db(press Y)?") + if(question == "Y"): + cursor = connection.cursor() + drop_cmd = f"""DROP TABLE {table}""" + cursor.execute(drop_cmd) + else: + print("abroting removing table") + sys.exit() +def importdb(file,importdb,table): + with sqlite3.connect(file) as connection: + print("db connection",connection.total_changes) + + cmd = f"""ATTACH "{importdb}" AS regions""" + cmd2 = f"""CREATE TABLE IF NOT EXISTS {table} AS SELECT * from regions.{table}""" + cmd_view = f""" + CREATE VIEW Canoton_Filter + AS + SELECT * FROM jobs as b + WHERE EXISTS + (SELECT GDENAME FROM {table} as w + where w.GDEKT = 'ZH' AND + b.location LIKE GDENAME);""" + cursor = connection.cursor() + cursor.execute(cmd) + print(cmd,cmd2) + cursor.execute(cmd2) + cursor.execute(cmd_view) + + print("db connection",connection.total_changes) + +def createnwview(file): + with sqlite3.connect(file) as connection: + cmd_create_nw_table = f"""CREATE VIEW "Nordwest-SCHWEIZ" AS SELECT * FROM jobs as b + WHERE EXISTS + (SELECT GDENAME FROM Cantons as w + where w.GDEKT = 'ZH' AND + b.location LIKE GDENAME) + OR EXISTS + (SELECT GDENAME FROM Cantons as w + where w.GDEKT = 'AG' AND + b.location LIKE GDENAME) + OR EXISTS + (SELECT GDENAME FROM Cantons as w + where w.GDEKT = 'SO' AND + b.location LIKE GDENAME)""" + cursor = connection.cursor() + cursor.execute(cmd_create_nw_table) + print("db connection",connection.total_changes) + +def writedb(jobs): + with sqlite3.connect("../db/sqlite3.db") as connection: + print("db connection", connection.total_changes) + cursor = connection.cursor() + # cursor.execute("CREATE TABLE jobs (title TEXT, location TEXT, company TEXT,link TEXT,hash INT)") + for i3,job in enumerate(jobs): + hash1 = mmh3.hash(job.title+job.company+job.location+job.date) + log(hash1); + if(cursor.execute("SELECT * FROM jobs WHERE hash = ?",(hash1,)).fetchone() != None): + log("Hash already exist") + else: + print("NEW_ENTRY") + cursor.execute("INSERT INTO jobs (star,tag,title,company,location,link,pubdate,hash) VALUES (?,?,?,?,?,?,?,?)",(job.starred,job.tag,job.title,job.company,job.location,job.link,job.date,hash1)) diff --git a/lib/gui.py b/lib/gui.py new file mode 100644 index 0000000..5826af1 --- /dev/null +++ b/lib/gui.py @@ -0,0 +1,297 @@ +from PySide6.QtWidgets import QApplication, QWidget, QMainWindow, QTableWidget, QVBoxLayout, QTableWidgetItem, QPushButton, QHBoxLayout, QTableView, QLineEdit, QDialog, QLabel, QTextEdit, QCheckBox, QComboBox +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtCore import QUrl,Qt,QSortFilterProxyModel, qDebug, QSize,QObject,QThread,Signal +from PySide6.QtSql import QSqlDatabase, QSqlTableModel, QSqlQueryModel, QSqlQuery + + +import sysparse +import sys + +Cantons = ["AG","ZH","BE","SG","SO"] + + +class Worker(QObject): + pwprompt = Signal() + pw = Signal(str) + finished = Signal() + dialog_closed = True + password = ['empty'] + + def run(self): + sysparse.parse(config="conf",worker=self) + def return_pw(self,x): + self.password = [x] + self.dialog_closed = True + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.w = None + + self.cmdCanton = '' + self.initcmd = 'SELECT * FROM jobs as b' + self.customcmd = '' + self.cmd = '' + self.setWindowTitle("DB_Inspector") + + self.layout = QVBoxLayout() + self.layout2 = QHBoxLayout() + + self.b_canton = QPushButton("Modify Filter") + self.b_canton.clicked.connect(self.showQueryWindow) + + self.browser = QWebEngineView() + self.browser.setUrl(QUrl("https://jobagent.ch")) + + self.EditQuery = QLineEdit() + self.EditQuery.returnPressed.connect(self.queryEditLine) + + self.model = QSqlTableModel(self) + self.model.setTable("jobs") + self.model.select() + + self.view = QTableView() + self.view.setModel(self.model) + self.setProxyViewSettings() + self.view.clicked.connect(self.cell_clicked) + + + self.PsyncDB = QPushButton("Perform sync acording to config file") + self.PsyncDB.clicked.connect(self.runWorker) + + self.layout.addWidget(self.view) + self.layout.addWidget(self.b_canton) + self.layout.addWidget(self.EditQuery) + self.layout.addWidget(self.PsyncDB) + self.layout2.addLayout(self.layout) + self.layout2.addWidget(self.browser) + + widget = QWidget() + widget.setLayout(self.layout2) + + self.setCentralWidget(widget) + + def setProxyViewSettings(self): + self.view.resizeColumnsToContents() + self.view.setColumnWidth(5,10) + self.view.hideColumn(7) + self.view.setSortingEnabled(True) + self.view.clicked.connect(self.cell_clicked) + def runWorker(self): + self.thread = QThread() + self.worker = Worker() + + self.worker.moveToThread(self.thread) + + self.thread.started.connect(self.disable_PsyncDB) + self.thread.started.connect(self.worker.run) + + self.worker.pwprompt.connect(self.showDialog) + self.worker.finished.connect(self.thread.quit) + self.worker.finished.connect(self.enable_PsyncDB) + + + self.thread.start() + def disable_PsyncDB(self): + self.PsyncDB.setText("Sync Running...") + self.PsyncDB.setEnabled(False) + def enable_PsyncDB(self): + self.PsyncDB.setEnabled(True) + self.PsyncDB.setText("Perform another sync acording to config file") + def showDialog(self): + w = PWPrompt() + w.set_MSG(self.worker.messageContent) + ret = w.exec() + self.pw = w.pw + self.worker.password = w.pw + print("showDialog,self.pw:",self.pw) + self.worker.dialog_closed=True + if ret == QDialog.Rejected: + return 1 + + def showQueryWindow(self,checked): + if self.w is None: + self.w = QueryWindow() + self.w.show() + def filter_canton(self,canton): + if canton != "ALL": + self.cmdCanton = f""" + WHERE EXISTS + (SELECT GDENAME FROM Cantons as w + where w.GDEKT = '{canton}' AND + b.location LIKE GDENAME) """ + print("cmd canton:", self.cmdCanton) + + else: + self.cmdCanton = ' ' + print("disable filter") + # self.customSQL(self.cmd) + + def queryEditLine(self): + self.cmd = self.EditQuery.text() + print(self.initcmd + self.cmdCanton +self.customcmd + self.cmd) + self.customSQL(self.initcmd+ self.cmdCanton + self.customcmd + self.cmd) + + def cell_clicked(self): + x = self.view.selectionModel().currentIndex().row() + y = self.view.selectionModel().currentIndex().column() + data = self.view.model().index(x,5).data() + print("cell clicked:",x," / ",y, "-->",data) + self.browser.setUrl(QUrl(data)) + + def customSQL(self,cmd): + print("Run SQL Query",cmd) + self.model.setTable("") + self.model.setQuery(cmd +" ;") + + self.proxymodel2 = QSortFilterProxyModel(self) + self.proxymodel2.setSourceModel(self.model) + self.view.setModel(self.proxymodel2) + self.setProxyViewSettings() +class PWPrompt(QDialog): + def __init__(self): + super().__init__() + self.pw = '' + self.MSG1 = QLabel("Please Enter Password") + self.MSG = QLabel("ACCOUNT") + self.BOK = QPushButton("OK") + self.BCancel = QPushButton("Cancel") + self.EPW = QLineEdit() + self.EPW.setEchoMode(QLineEdit.EchoMode.Password) + self.BOK.clicked.connect(self.confirm) + self.BCancel.clicked.connect(self.reject) + + self.VLayout = QVBoxLayout() + self.VLayout.addWidget(self.MSG1) + self.VLayout.addWidget(self.MSG) + self.VLayout.addWidget(self.EPW) + self.VLayout.addWidget(self.BOK) + self.VLayout.addWidget(self.BCancel) + + self.setLayout(self.VLayout) + def confirm(self): + self.accept() + self.pw = self.EPW.text() + def set_MSG(self,message): + self.MSG.setText(message) + +class QueryWindow(QWidget): + def __init__(self): + super().__init__() + + self.FlagShow = 0 + + self.label = QLabel("Query settings") + self.setWindowTitle("Query") + + self.EditQuery = QTextEdit() + self.BSubmit = QPushButton("Submit") + self.BSubmit.clicked.connect(self.submit) + + self.LFilter = QLabel() + self.LFilter.setText("Filter by Cantons") + + self.CFilter = QComboBox() + self.CFilter.addItem("ALL") + for Canton in Cantons: + self.CFilter.addItem(Canton) + self.CFilter.currentTextChanged.connect(window.filter_canton) + self.CFilter.currentTextChanged.connect(self.setTFilter) + + self.TFilter = QTextEdit() + self.TFilter.setReadOnly(True) + self.TInitCmd = QLabel() + self.TInitCmd.setText(window.initcmd) + + self.vLayout = QVBoxLayout() + self.vLayout.addWidget(self.TInitCmd) + self.vLayout.addWidget(self.TFilter) + self.vLayout.addWidget(self.EditQuery) + self.vLayout.addWidget(self.BSubmit) + + self.LShowViews = QLabel() + self.LShowViews.setText("Custom Views in Database") + + self.CShowViews = QComboBox() + items = self.getViews() + for item in items: + self.CShowViews.addItem(item) + self.CShowViews.currentTextChanged.connect(self.setView) + + self.PApplyView = QCheckBox() + self.PApplyView.setText("Apply View") + self.PApplyView.clicked.connect(self.setView) + + + self.vrLayout = QVBoxLayout() + self.vrLayout.addWidget(self.LFilter) + self.vrLayout.addWidget(self.CFilter) + self.vrLayout.addWidget(self.LShowViews) + self.vrLayout.addWidget(self.CShowViews) + self.vrLayout.addWidget(self.PApplyView) + + self.WvrLayout = QWidget() + self.WvrLayout.setLayout(self.vrLayout) + self.WvrLayout.setMaximumSize(QSize(200,200)) + + + self.hLayout = QHBoxLayout() + self.hLayout.addLayout(self.vLayout) + self.hLayout.addWidget(self.WvrLayout) + + widget = QWidget() + self.setLayout(self.hLayout) + self.EditQuery.setText(window.customcmd) + + print("Comboshowview:",self.CShowViews.currentText()) + + def getViews(self): + item = [] + statement = f"""SELECT name FROM sqlite_master where type='view'""" + query = QSqlQuery(statement) + while query.next(): + print(query.value(0)) + item.append(query.value(0)) + + print(query.lastError()) + return item + + def setView(self): + if self.PApplyView.isChecked(): + self.view = self.CShowViews.currentText() + print("Selected View:",self.view) + window.initcmd = f"""SELECT * FROM '{self.view}'""" + print("window.initcmd:", window.initcmd) + else: + window.initcmd = f"""SELECT * FROM jobs as b """ + print("View unchecked") + self.TInitCmd.setText(window.initcmd) + + def setTFilter(self): + self.TFilter.setText(window.cmdCanton) + + def submit(self): + self.setView() + window.customcmd = self.EditQuery.toPlainText() + window.queryEditLine() + #print("text:",window.customcmd) + #window.customSQL(window.customcmd) + self.hide() + + def out(self,s): + print("Current selection",s) + + +app = QApplication(sys.argv) + +con = QSqlDatabase.addDatabase("QSQLITE") +con.setDatabaseName("../db/sqlite3.db") + +if not con.open(): + qDebug("Error on opening sql database") + sys.exit(1) + +window = MainWindow() +window.show() +app.exec() + diff --git a/lib/helpers.py b/lib/helpers.py new file mode 100644 index 0000000..81f8e3c --- /dev/null +++ b/lib/helpers.py @@ -0,0 +1,166 @@ +import string +import requests +from bs4 import BeautifulSoup +from enum import Enum +import re +from dateconverter import * +from datetime import datetime +DEBUG = False + +def log(*s): + if DEBUG: + print(s) +class mode(): + #def set(self,flag,value): + # self.flag = flag + # self.value = value + #def __init__(self,): + DEFAULT = 0 + LINK = 0 + LOCATION_CLEANUP = 0 + SWAPDATE = 0 + CLEANDATE = 0 + ATTRS = 0 +months = [ + ('January','01'), + ('February','02'), + ('March','03'), + ('April','04'), + ('May','05'), + ('June','06'), + ('July','07'), + ('August','08'), + ('September','09'), + ('October','10'), + ('November','11'), + ('December','12')] +class item(): + def __init__(self,tag,tag_content,index): + self.tag = tag + self.tag_content = tag_content + self.index = index + +class job(): + def __init__(self,title,profession,company,location,date,description,link,tag,starred): + self.title = title + self.profession = profession + self.company = company + self.location = location + self.date = date + self.description = description + self.link = link + self.tag = tag + self.starred = starred + + def __str__(self): + return "%s| %s|%s|%s|%s|%s|%s" % (self.title,self.profession,self.company,self.location,self.date,self.description,self.link) + +def finder(results,item,**modes): + ATTRS = modes.get('ATTRS',0) + LOCATION_CLEANUP = modes.get('LOCATION_CLEANUP',0) + LINK = modes.get('LINK',0) + SWAPDATE = modes.get('SWAPDATE',0) + CLEANDATE = modes.get('CLEANDATE',0) + BASEURL = modes.get('BASEURL','') + content = [] + i = item.index + log("Modes:",modes) + + for entry in results: + if ATTRS==1: + result = entry.findAll(item.tag,attrs=item.tag_content) + log(item.tag_content) + else: + result = entry.findAll(item.tag,class_=item.tag_content) + log("found:",len(result)) + if result: + log("theres a result") + if i>(len(result)-1): + log("len:",len(result)-1,"i:",i) + log("index out of bounds fall back to the %d count",i) + # input("Press Enter..") + i=(len(result)-1) + result2 = result[i] + if LOCATION_CLEANUP==1: + location = CleanLocation(result2.text.strip()) + content.append(location) + elif LINK==1: + string = result2.get("href") + if BASEURL: + string = BASEURL+string + content.append(string) + elif SWAPDATE==1: + content.append(DateCHToUS(result2.text.strip())) + elif CLEANDATE==1: + content.append(jobs_ch_clean_date(result2.text.strip())) + else: + content.append(result2.text.strip()) + if not result: + if CLEANDATE: + today = datetime.today().strftime('%Y-%M-%D') + content.append(today) + content.append("NOTFound") + return content + + +def CleanLocation(location): + #p = re.compile('CH-[0-9]{4}') + location = re.sub('CH-[0-9]{4}|[0-9]{4}| ','',location) + return location + +def arrayToClass(titles,companys,locations,dates,links,tag): + jobs = [] + if(len(titles) == len(companys) == len(locations) == len(dates)): + log("len:",len(titles)) + for i, title in enumerate(titles): + jobs.append(job(title,"test_prof",companys[i],locations[i],dates[i],"test_desc",links[i],tag,0)) + log(jobs[i]) + return jobs + else: + print("Something went wrong unequal length of data arrays") + return 0 +def jobs_ch_clean_date(date): + newdate='' + + for i in range(11,len(date)):#remove string "Published:" + newdate+=date[i] + + newdate2 = jobs_ch_switch_month(newdate) + return newdate2 + +def jobs_ch_switch_month(date): + newdate='' + newmonth='' + day = '' + year = '' + + for i in range(3,len(date)-5): + newmonth += date[i] + for month in months: + if(month[0] == newmonth): + newmonth = month[1] + + for i in range(0,2): + day+=date[i] + for i in range(len(date)-2,len(date)): + year += date[i] + newdate = '20'+year+'-'+newmonth+'-'+day + return newdate + +def CleanLocation(location): + #p = re.compile('CH-[0-9]{4}') + location = re.sub('CH-[0-9]{4}|[0-9]{4}| ','',location) + return location + +def extractDomain(url): + pattern = r'https:\/\/.*\..+?(?=\/)' + domain = re.match(pattern,url) + if domain: + return domain.group() + else: + return 0 + +def makeSession(url): + with requests.Session() as session: + page = session.get(url) + return session diff --git a/lib/login.py b/lib/login.py new file mode 100644 index 0000000..106efa1 --- /dev/null +++ b/lib/login.py @@ -0,0 +1,38 @@ +import requests +from helpers import * +def login(entry): + user = entry.user + pw = entry.pw + loginurl = entry.loginurl + scrapurl = entry.scrapurl + with requests.Session() as session: + headers = { + "Host": "www.jobagent.ch", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": "58", + "Origin": "https://www.jobagent.ch", + # "Connection": "keep-alive", + "Referer": "https://www.jobagent.ch/user/login", + # "Upgrade-Insecure-Requests": "1", + # "Sec-Fetch-Dest": "document", + # "Sec-Fetch-Mode": "navigate", + #"Sec-Fetch-Site": "same-origin", + # "DNT": "1", + # "Sec-GPC": "1" + } + + r = session.get(loginurl) + payload = {"redirectUrl":"","email":user,"password":pw} + resp = session.post(loginurl,data=payload,headers=headers) + print(payload) + print("response from login attempt",resp.url) + if resp.url == 'https://www.jobagent.ch/user/login?error': + print("Error on login") + return -1 + r = session.get(scrapurl) + + return session diff --git a/lib/main.py b/lib/main.py new file mode 100644 index 0000000..2646499 --- /dev/null +++ b/lib/main.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from sysparse import parse + +parse() + + diff --git a/lib/scrap_jobs.py b/lib/scrap_jobs.py new file mode 100644 index 0000000..5ed9bac --- /dev/null +++ b/lib/scrap_jobs.py @@ -0,0 +1,156 @@ +from helpers import * +DEBUG = False + +def log(*s): + if DEBUG: + print(s) +def indeed_com(url,session): + jobs = [] + if(session == 0): + with requests.Session() as session: + page = session.get(url) + print(page) + else: + page = session.get(url) + print(page) + soup = BeautifulSoup(page.content,"html.parser") + #print(soup.prettify()) + + results = soup.find_all("li",class_= 'css-5lfssm eu4oa1w0') + + location = item("p",{'data-testid':'text-location'},0) + ar_location = finder(results,location,LOCATION_CLEANUP=1,ATTRS=1) + + company = item("p",{'data-testid':'company-name'},0) + ar_company = finder(results,location,ATTRS=1) + + title = item("a",'jobTitle',0) + ar_title = finder(results,location) + + date = item("span","Span-sc-1ybanni-0 Text__span-sc-1lu7urs-12 Text-sc-1lu7urs-13 krGudM hUhFmL",0) + ar_date = finder(results,date,CLEANDATE=1) + +def scrap_jobs(url,entry,session): + jobs = [] + log("in scrap jobs,url",url) + if(session == 0): + with requests.Session() as session: + page = session.get(url) + log(page) + else: + page = session.get(url) + log(page) + soup = BeautifulSoup(page.content,"html.parser") + #print(soup.prettify()) + + results = soup.find_all("div",attrs={"data-feat":"searched_jobs"}) + + location_class = "P-sc-hyu5hk-0 Text__p2-sc-1lu7urs-10 Span-sc-1ybanni-0 Text__span-sc-1lu7urs-12 Text-sc-1lu7urs-13 jZCxUn" + location = item("p",location_class,0) + ar_location = finder(results,location,LOCATION_CLEANUP=1) + + company_class = "P-sc-hyu5hk-0 Text__p2-sc-1lu7urs-10 Span-sc-1ybanni-0 Text__span-sc-1lu7urs-12 Text-sc-1lu7urs-13 jZCxUn" + company = item("p",company_class,3) + ar_company = finder(results,company,DEFAULT=1) + + title = item("span","Span-sc-1ybanni-0 Text__span-sc-1lu7urs-12 Text-sc-1lu7urs-13 VacancyItem___StyledText2-sc-iugtv6-5 iaJYDR jlFpCz dMwMcR",0) + ar_title = finder(results,title,DEFAULT=1) + + date = item("span","Span-sc-1ybanni-0 Text__span-sc-1lu7urs-12 Text-sc-1lu7urs-13 krGudM hUhFmL",0) + ar_date = finder(results,date,CLEANDATE=1) + + link = item("a","Link__ExtendedRR6Link-sc-czsz28-1 khAvCu Link-sc-czsz28-2 VacancyLink___StyledLink-sc-ufp08j-0 dXKwhi dDgwgk",0) + ar_link = finder(results,link,LINK=1,BASEURL="https://jobs.ch") + + tag = entry.tag#get from config + return arrayToClass(ar_title,ar_company,ar_location,ar_date,ar_link,tag) + +def next_url_jobs_ch(url,session,baseurl): + next_link_str = '' + if(session == 0): + with requests.Session() as session: + page = session.get(url) + else: + page = requests.get(url) + soup = BeautifulSoup(page.content,"html.parser") + result_next = soup.findAll("div",attrs={"data-cy":"paginator"}) + next_=item("a",{"data-cy":"paginator-next"},0) + next_link = finder(result_next,next_,ATTRS=1,LINK=1) + if next_link: + if(next_link[0] != "NOTFound"): + next_link_str = str(next_link[0]) + next_link_str = baseurl + next_link_str + log(next_link_str) + else: + return 0 + if next_link_str != '': + return next_link_str + else: + return 0 + +def next_url_jobagent(base_url,session,c):#depreacted will be removed in the future + found = False + + if(session == 0): + with requests.Session() as session: + page = session.get(base_url) + else: + page = requests.get(base_url) + + soup = BeautifulSoup(page.content,"html.parser") + results = soup.find("ul",class_="pagination") + + if(results != None): + pages = results.text + if(results == None): + print("pagination next not found, probably end of pages:") + + next_url_names = soup.find_all("a",class_="btn btn-sm btn-secondary") + for i2 in next_url_names: + striped_string = i2.text.strip() + log(i2.text.strip(),"stripped:",striped_string) + # print("Printable characters?",striped_string.isprintable()) + if (striped_string) == "Nächste Seite": + log(i2) + next_url = i2.get("href") + log("url of next site") + found = True + return next_url + break + + if found == False: + return 0 + +def scrap_jobagent(url,entry,session): + jobs = [] + log("in scrap jobs,url",url) + if(session == 0): + with requests.Session() as session: + page = session.get(url) + log(page) + else: + page = session.get(url) + log(page) + soup = BeautifulSoup(page.content,"html.parser") + #print(soup.prettify()) + + results = soup.find_all("li",class_="item") + + title = item("span","jobtitle",0) + ar_title = finder(results,title) + + location = item("span","location",0) + ar_location = finder(results,location,LOCATION_CLEANUP=1) + + company = item("span","company",0) + ar_company = finder(results,company,DEFAULT=1) + + link = item("a","title",0) + ar_link = finder(results,link,LINK=1) + + date = item("span","pubdate",0) + ar_date = finder(results,date,SWAPDATE=1) + tag = entry.tag + + return arrayToClass(ar_title,ar_company,ar_location,ar_date,ar_link,tag) + diff --git a/lib/sysparse.py b/lib/sysparse.py new file mode 100644 index 0000000..6535fbd --- /dev/null +++ b/lib/sysparse.py @@ -0,0 +1,105 @@ +import argparse +import config +import sys +from enum import IntEnum +from scrap_jobs import * +from login import * +from time import sleep +from db import * + +def choose_scraper(entry,session): + if not session: + session = requests.Session() + domain = extractDomain(entry.scrapurl) + match domain: + case 'https://www.jobs.ch': + runner(entry,session,scrap_jobs,next_url_jobs_ch) + case 'https://software-job.ch': + runner(entry,session,scrap_jobagent,next_url_jobagent) + case 'https://www.jobagent.ch': + runner(entry,session,scrap_jobagent,next_url_jobagent) + +def parse(**kwargs): + session=0 + if len(sys.argv)>1: + worker=0 + parser = argparse.ArgumentParser() + parser.add_argument("-c","--config",help = "Specific a config file to use,from where to scrap the jobs") + parser.add_argument("-t","--test",help = "only for test purposes while developing",action="store_true") + parser.add_argument("--importregiondb",help = "Import a database used for querring by Regions or Cantons",action="store_true") + parser.add_argument("--initdb",help = "Initialice a new db from scratch without entrys",action="store_true") + parser.add_argument("--rmdb",help = "!!reove existing db!!DATALOSS!!",action="store_true") + # parser.add_argument("--help",help = "print help") + parser.add_argument("--login",nargs=3,help = "login by specifing login and passwor by a given url",metavar=('USERNAME','PASSWORD','URL')) + parser.add_argument("--createnwview",help = "Create a VIEW for the Region Nordwest Schweiz",action="store_true") + args = parser.parse_args() + + if args.test: + session = makeSession(sys.argv[args.test]) + choose_scraper(arg.test,session) + if args.importregiondb: + importdb("../db/sqlite3.db","../db/Cantons.db","Cantons") + if args.initdb: + initdb("../db/sqlite3.db") + if args.rmdb: + rmdb("../db/sqlite3.db","jobs") + if args.login: + user,pw,url = args.login + session = login(user,pw,url,url) + choose_scraper(url,session) + if args.config: + login_loop(args.config,False,worker) + if args.createnwview: + createnwview("../db/sqlite3.db") + + if len(kwargs)>0: + print("no sysargs fiven, running as a module") + vconfig = kwargs.get('config') + worker = kwargs.get('worker') + print("config:",vconfig) + if vconfig: + login_loop(vconfig,True,worker) + worker.finished.emit() + print("finished sync job") + + +def login_loop(config_file,gui,worker): + ret = -1 + ret_login = 0 + session = 0 + while (ret != 0): + ret = entry2 = config.readConfig(config_file,gui,worker) + print(entry2) + if(ret != 0 and ret_login != 1): + if(entry2.loginurl != 'NONE'): + session = -1 + while session == -1: + session = login(entry2) + if session == -1: + ret_login = entry2.input_pw(gui,entry2.user,worker) + choose_scraper(entry2,session) + +def runner(entry,session,scrap_func,next_url_func): + i=0 + b_url = entry.scrapurl + while b_url != 0 and i<50: + sleep(0.3) + if b_url: + domain = extractDomain(b_url) + print(domain) + if domain == 'https://www.jobagent.ch' or domain == 'https://software-job.ch': + jobs = scrap_func(b_url,entry,session) + writedb(jobs) + b_url = next_url_func(b_url,session,0) + elif domain == 'https://www.jobs.ch': + jobs = scrap_func(b_url,entry,session) + writedb(jobs) + b_url = next_url_func(b_url,session,"https://www.jobs.ch") + + if b_url != 0: + print("main:" + b_url) + if b_url==0: + print("End of listed items, or did not find any other Nächste Seite Buttons") + + i=i+1 + print(i) diff --git a/querry.note b/querry.note new file mode 100644 index 0000000..a199a96 --- /dev/null +++ b/querry.note @@ -0,0 +1,4 @@ +ATTACH Cantons.db AS Cantons +ATTACH 2.db AS db2 + +SELECT * FROM Jobs.jobs as b WHERE EXISTS (SELECT GDENAME FROM Cantons.cantons as w where w.GDEKT = 'ZH' AND b.location LIKE GDENAME); diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..95fc17e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +beautifulsoup4==4.12.3 +mmh3==4.1.0 +numpy==1.26.4 +Requests==2.31.0 +pyside6