Internet Searches With Python and PyQt5

ZennDogg
6 min readJul 17, 2021
Clock with date and weather forecasts GUI written in python anf pyqt5.

One of the projects incorporated in my desktop menuing system is an internet search option. Everyone should have a quick and easy way of looking up information of all kinds on the internet. Begin by clicking on “SEARCH”.

The following drop-down search window appears.

The upper black section is the input section. Notice the cursor. The lower section consists of five menu choices. Type in the subject and click on your choice. ‘DUCK’ opens duckduckgo.com, my browser of choice. ‘EBAY’, ‘AMAZON’, and ‘YOUTUBE’ open the respective sites with your search parameters. ‘WIKI’ opens a window on the desktop with up to a 25 sentence synopsis of your query.

If a subject is not entered, clicking on the menu choices opens the website’s home page, except the menu option ‘WIKI’ which gives an error message box and resets itself for the next query.

Now for the fun part: Coding!

As usual, we start with the import statements.

import os
import sys
import time
import wikipedia
import webbrowser
import subprocess

from PyQt5.QtCore import QRect, pyqtSignal, QObject, QThread, QEventLoop
from PyQt5.QtWidgets import QMainWindow, QMessageBox
from qtpy_cfg import *

The file qtpy_cfg is a mixin file (support file of functions). The functions used are as follows:

def qblack(self):
self.setWindowFlags(Qt.FramelessWindowHint)
self.setGeometry(self.left, self.top, self.width, self.height)
self.setAutoFillBackground(True)
p = self.palette()
p.setColor(self.backgroundRole(), Qt.black)
self.setPalette(p)
def qtbutton(self, name, x, y, w, h, style, font, title, connection):
self.name = QPushButton(self)
self.name.setStyleSheet(style)
self.name.setFont(font)
self.name.setText(title)
self.name.setGeometry(x, y, w, h)
self.name.clicked.connect(connection)
def qbutton_dim(self):
return "QPushButton {color: rgba(54, 136, 200, 250); " "background-color: black; }"
def qledit(self):
return (
"QLineEdit {background-color: black; font: normal; "
"color: rgba(162,201,229,255); border: black;}"
)

The search window is on a 30-second timer. If no action is taken, the window closes after 30 seconds. This is the class for that timer initiated on its own thread.

class TimerThread(QThread):

def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.timer = QTimer()
self.timer.moveToThread(self)

def closeThread(self):
sys.exit()

def run(self):
self.timer.singleShot(30000, self.closeThread)
loop = QEventLoop()
loop.exec_()

Speaking of threads, we need a threading class to handle our requested option.

class CloneThread(QThread):
def __init__(self):
QThread.__init__(self)
self.filename = ""

def run(self):
if "http" not in self.filename:
os.system(f'start /min {self.filename}')
else:
webbrowser.open(self.filename)

The menu choices are either websites or programs, hence the if/else in the run() function.

Our next class provides a signal slot for closing an application. It is really short.

class Communicate(QObject):
closeApp = pyqtSignal()

Finally, we get to the GUI class. Here we set up the size and position parameters and initialize the previously written classes.

class Search(QMainWindow):
def __init__(self):
"""###
:rtype: object
"""
super().__init__()

self.left = 602
self.top = 0
self.width = 200
self.height = 45
self.comm = Communicate()
self.my_thread = CloneThread()
self.timer_thread = TimerThread()
self.comm.closeApp.connect(self.close)
self.initUI()

This next section builds the widgets (buttons, label, and timer).

def initUI(self):

qblack(self)
canda_8 = QFont("Candalara", 8)
qdim = qbutton_dim(self)
q_ledit = qledit(self)

self.inputTxt = QLineEdit(self)
self.inputTxt.setStyleSheet(q_ledit)
self.inputTxt.setFont(QFont("Inconsolata", 12))
self.inputTxt.setGeometry(QRect(4, 2, 200, 20))

qtbutton(self, "ebay", 38, 22, 30, 30, qdim, canda_8, "EBAY", self.ebay)
qtbutton(self, "duck", 4, 22, 30, 30, qdim, canda_8, "DUCK", self.duck)
qtbutton(self, "amazon", 69, 22, 48, 30, qdim, canda_8, "AMAZON", self.amazon)
qtbutton(self, "youtube", 121, 22, 48, 30, qdim, canda_8, "YOUTUBE", self.youtube)
qtbutton(self, "wiki", 174, 22, 30, 30, qdim, canda_8, "WIKI", self.wiki )

self.timer_thread.start()
self.mousePressEvent()

Here you can see the qblack() and qtbutton() functions imported from qtpy_cfg. We’ve also coded the input box. The 30-second timer is started and then the window closes via self.mousePressEvent(). Here is that function.

def mousePressEvent(self):
self.comm.closeApp.emit()

It emits the closeApp signal to, obviously, close the app.

Above was mentioned the behavior for calling an option without a subject. This function controls that behavior for ‘DUCK’ when pressing the ‘Enter’ or ‘Return’ keys.

def keyPressEvent(self, e):
if e.key() == Qt.Key_Return or e.key() == Qt.Key_Enter:
self.duck()

Now we need a function for threading within this class.

def threads(self, filename):
self.my_thread.filename = filename
self.my_thread.start()

The next function is a housekeeping function.

def wrap_up(self, num):
time.sleep(num)
self.mousePressEvent()

I noticed that none of the sites would open when clicking on the menu options. A time lag needed to be added to give the internet time to respond. Then the window is closed.

Now we add functionality to the five menu buttons. The first is for ‘DUCK’.

def duck(self):
self.inputTxt.keyPressEvent = self.keyPressEvent
mySearch = self.inputTxt.text()
if mySearch == "":
self.threads("https://duckduckgo.com")
else:
self.threads(
"https://duckduckgo.com/?q=" + mySearch + "&t=ffnt&atb=v180-1&ia=web"
)
self.inputTxt.clear()
self.wrap_up(0.8)

mySearch is the query typed in the search box. If no subject, open the home page. If there is a subject, search the web for that subject. Then wrap_up() does its thing. The functions for ‘EBAY’, ‘AMAZON’, and ‘YOUTUBE are similar.

def amazon(self):
mySearch = self.inputTxt.text()
self.threads(
"https://smile.amazon.com/s?k="
+ mySearch
+ "&crid=32GWUOBI90QY4&sprefix=%EF%BF%BD"
+ mySearch
+ "%2Caps%2C207&ref=nb_sb_ss_i_3_5"
)
self.wrap_up(0.8)

def ebay(self):
mySearch = self.inputTxt.text()
self.threads("Search https://www.ebay.com " + mySearch)
self.wrap_up(0.8)

def youtube(self):
mySearch = self.inputTxt.text()
self.threads("https://www.youtube.com/results?search_query=" + mySearch)
self.wrap_up(0.8)

Clicking on any of the buttons above without a subject opens the home page of that site.

The function for the ‘WIKI’ button is a bit different.

def wiki(self):
mySearch = self.inputTxt.text()

try:
data = wikipedia.summary( mySearch, sentences=25 )
except Exception:
data = ''
buttonReply = QMessageBox.warning(
self,
"Subject Not Found",
f'Subject "{mySearch}" does not match any pages. \nBe more specific or try another subject!',
QMessageBox.Ok,
QMessageBox.Ok,
)

if data != '':
if os.path.exists( 'wiki.txt' ):
os.remove( 'wiki.txt' )
with open( 'wiki.txt', 'a', encoding='utf-8' ) as f:
f.write(data)
self.threads( r'pythonw C:\Users\mount\source\repos\MyDashboard\MENU\wiki.pyw' )
self.hide()
else:
self.inputTxt.clear()
self.inputTxt.setFocus()
self.timer_thread.start()
self.update()

Wikipedia is huge, but it doesn’t cover everything. Errors are more than likely when researching a subject. For instance, search Wikipedia for ‘Springfield’ and it will likely throw an error (how many different Springfields can you think of). Because of this, error handling is an important part of the function. The try/except protocol does this for us. When the subject results in an error, the variable data is set to null and the message box is displayed. Clicking on ‘Ok’ closes the message box.

If data is present, write the response to a file (‘wiki.txt’) and open ‘wiki.pyw’, a pyqt5 GUI, to display the text. If data is null, reset the search GUI for the next query.

The last bit of code runs the actual file.

if __name__ == "__main__":
app = QApplication([])
ex = Search()
ex.show()
sys.exit(app.exec_())

This completes the search GUI of this article. Next, we code the window and display the text. The name of that file is ‘wiki.pyw’.

import sys, os
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QApplication, QPushButton, QMainWindow, QLabel, QScrollArea, QFrame
from qtpy_cfg import qblack, qbutton_dim, qtbutton, qlabel_gray, qlabel_ltGray


class Communicate(QObject):
closeApp = pyqtSignal()

Notice there are two more functions from qtpy_cfg. These are:

def qlabel_gray(self):
return "QLabel {color: rgba(162,201,229,255); background-color: black;}"
def qlabel_ltGray(self):
return "QLabel {color: rgb(130,130,130); background-color: black;}"

Now, the GUI class.

class Wiki(QMainWindow):

def __init__(self):
super().__init__()
self.left = 500
self.top = 200
self.width = 850
self.height = 500
self.comm = Communicate()
self.comm.closeApp.connect( self.close )

qblack(self)
qdim = qbutton_dim(self)
q_gray = qlabel_ltGray(self)
canda_10 = QFont( "Candalara", 10 )
helv_13 = QFont( "Helvetica", 13 )

with open( 'wiki.txt', 'r', encoding='utf-8') as f:
text = f.read()

The last two lines read the data from ‘wiki.txt’. This GUI has one button, one label, and one ScrollArea, as follows:

self.wiki_label = QLabel( self )
self.wiki_label.setStyleSheet( q_gray)
self.wiki_label.setText(text)
self.wiki_label.setWordWrap(True)
self.wiki_label.setFont( helv_13 )
self.wiki_label.setGeometry( 0, 0, 730, 450 )

self.wiki_scroll = QScrollArea( self )
self.wiki_scroll.setFrameShape( QFrame.NoFrame )# No frame border
self.wiki_scroll.setWidgetResizable( True )
self.wiki_scroll.setWidget( self.wiki_label )
self.wiki_scroll.setStyleSheet( "QScrollArea {min-width:800 px; min-height: 430px; }" )
self.wiki_scroll.setGeometry( 25, 25, 650, 400 )

qtbutton(self, 'x', 700, 465, 60, 30, qdim, canda_10, 'FINISHED', self.done)

and finally:

    def mousePressEvent(self):
self.comm.closeApp.emit()

def done(self):
self.mousePressEvent()


if __name__ == '__main__':
app = QApplication( [] )
ex = Wiki()
ex.show()
sys.exit( app.exec_() )

If you enjoy reading stories like these and want to support me as a writer, consider subscribing to Medium for $5 a month. As a member, you have unlimited access to stories on Medium. If you sign up using my link, I’ll earn a small commission.

--

--

ZennDogg

Retired military, Retired US Postal Service, Defender of the US Constitution from all enemies, foreign and domestic, Self-taught in python