From 9d96d9e7c3d4bc8fb64ea8e27d97b85cefad1a7f Mon Sep 17 00:00:00 2001 From: ccppi Date: Thu, 13 Jun 2024 11:14:04 +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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 Usiiror4`6*B8)aNfNdrK0F}-{WdHyG literal 0 HcmV?d00001 diff --git a/example.txt b/example.txt new file mode 100644 index 0000000..3cd703b --- /dev/null +++ b/example.txt @@ -0,0 +1 @@ +python main.py --login EMAIL PW https://www.jobagent.ch/search?terms=Informatiker-Jobs&provinces=AG%2CSO&workload=40-60 diff --git a/lib/conf b/lib/conf new file mode 100644 index 0000000..1bcddef --- /dev/null +++ b/lib/conf @@ -0,0 +1,55 @@ + +[jobagent.ch] +USER = test@gmx.ch +PW = ASK +LOGINURL = https://www.jobagent.ch/user/login +SCRAPURL = https://www.jobagent.ch/search?terms=Automatiker&lra=0&as=0 +TAG = Automatiker + + +[software-job.ch-application-engineer] +USER = NONE +PW = NONE +LOGINURL = NONE +SCRAPURL = https://software-job.ch/application-engineer +TAG = Informatiker + +[software-job.ch] +USER = NONE +PW = NONE +LOGINURL = NONE +SCRAPURL = https://software-job.ch/python-entwicklung +TAG = Informatiker,Python + +[jobs.ch_linux] +USER = NONE +PW = NONE +LOGINURL = NONE +SCRAPURL = https://www.jobs.ch/en/vacancies/?term=linux +TAG = Informatiker,Linux + + +[jobagent.ch-2] +USER = test@gmx.ch +PW = ASK +LOGINURL = https://www.jobagent.ch/user/login +SCRAPURL = https://www.jobagent.ch/search?terms=Informatiker&lra=0&as=0 +TAG = Informatiker + +[jobs.ch] +USER = NONE +PW = NONE +LOGINURL = NONE +SCRAPURL= https://www.jobs.ch/en/vacancies/?term=automatiker +TAG = Automatiker + +[jobs.ch_informatiker] +USER = NONE +PW = NONE +LOGINURL = NONE +SCRAPURL= https://www.jobs.ch/en/vacancies/?term=informatiker +TAG = Informatiker + + + +#https://www.jobagent.ch/search?terms=Automatiker&workload=60-100&lra=0&as=0 diff --git a/lib/config.py b/lib/config.py new file mode 100644 index 0000000..9333b26 --- /dev/null +++ b/lib/config.py @@ -0,0 +1,56 @@ +import time +import configparser +from PySide6.QtWidgets import QDialog,QPushButton, QLineEdit,QVBoxLayout, QLabel + + +class Entry: + user=0 + pw=0 + loginurl=0 + scrapurl=0 + tag=0 + def __str__(self): + return "values from Entry: %s %s PW %s %s" %(self.tag,self.user,self.loginurl,self.scrapurl) + def input_pw(self,gui,message,worker): + self.gui=gui + if not self.gui: + self.pw = input("Enter your Password") + if self.gui: + worker.messageContent = self.scrapurl + worker.dialog_closed=False + worker.pwprompt.emit() #signal to mainthread + while not worker.dialog_closed: + time.sleep(1) + pass + self.pw = worker.password + +def readConfig(file,gui,worker): + if not hasattr(readConfig,"counter"): + readConfig.counter = -1 + + print(readConfig.counter) + entry = Entry() + config = configparser.RawConfigParser() + buffer = config.read(file) + print("buffer:",buffer) + sections = config.sections() + + if(readConfig.counter < (len(sections)-1)): + readConfig.counter += 1 + else: + readConfig.counter = -1 + return 0 + + entry.user = config[sections[readConfig.counter]]["USER"] + entry.pw = config[sections[readConfig.counter]]["PW"] + entry.scrapurl = config[sections[readConfig.counter]]["SCRAPURL"] + entry.tag = config[sections[readConfig.counter]]["TAG"] + if(entry.user != 0): + if(entry.pw == "ASK"): + entry.input_pw(gui,entry.user,worker) + entry.loginurl = config[sections[readConfig.counter]]["LOGINURL"] + + + print(entry) + return entry + diff --git a/lib/dateconverter.py b/lib/dateconverter.py new file mode 100644 index 0000000..9081d39 --- /dev/null +++ b/lib/dateconverter.py @@ -0,0 +1,14 @@ +def DateCHToUS(date): + #01.02.2010 --> 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