Use Python and Selenium to output text of only selected index - python

I'm having some trouble with creating a program that automates a checkout process. I'm using python 3 along with Selenium. The program parses through a range of dates, which are outputted on the page as available four available slots. If none are available on the current page, it will click the 'next' button and search through the next four dates. If it gets to the end of the available date ranges and finds nothing, it'll wait thirty seconds and reset and do it all over again.
I've got the majority of this done, except for two issues:
1) I'm trying to add an argument that, when included, will go beyond the base functionality (which is to simply notify the user via text using Twilio), and complete the full checkout process.
This is the python code I'm using:
def find_available(args):
dates_available = True
spaces_free = False
free_spaces = ""
while not spaces_free:
while dates_available:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
free_spaces = selected_spot.text.replace('Select a time', '').strip()
spaces_free = True
else:
print(selected_spot.text.replace('Select a time', '').strip())
if spaces_free:
print('Slots Available!')
if args.checkout:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
driver.find_element_by_xpath("//*[contains(text(), 'Soonest available')]").click()
time.sleep(1.5)
driver.find_element_by_xpath("//input[#type='submit' and #value='Continue']").click()
print('Your order has been placed!')
else:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
print('Your order time will be held for the next hour. Check your date and confirm!')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="auto-checkout")
parser.add_argument('--checkout', '-c', action='store_true',
help="Select first available slot and checkout")
args = parser.parse_args()
find_available(args)
Expected Behavior
If the program is launched using the '--checkout' or '-c' argument, then, once 'spaces-free' is set to true, it should send a text with the text within the 'free_spaces' element. It should then move on to the next phase, which would be a selecting of a radio button that contains the text 'Soonest available' (as in select the first available radio button that contains an available time slot), and then click the continue button.
Actual Behavior
The program will run, find an available time slot, then simply move on to the next days, never attempting to select a radio button and move forward in the checkout process.
What am I doing wrong?
Any help would be appreciated.

it seems to me that you never set the dates_available to False inside your while loop:
while dates_available:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
free_spaces = selected_spot.text.replace('Select a time', '').strip()
spaces_free = True
else:
print(selected_spot.text.replace('Select a time', '').strip())
So you'll never exit the while loop. If you don't want to rewrite the whole logic, you could set dates_available = False right after you set spaces_free = True. That would allow exiting the while loop, but you might need a break or two to exit the for loops too.
If you want a failsafe behavior, you should refactor your code for smaller functions and if you want only the first available something, you could just return from the function with the first available data.
Something like this maybe?
def find_available(args):
def get_a_date():
while True:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
return selected_spot.text.replace('Select a time', '').strip()
else:
print(selected_spot.text.replace('Select a time', '').strip())
free_spaces = get_a_date()
print('Slots Available!')
if args.checkout:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
driver.find_element_by_xpath("//*[contains(text(), 'Soonest available')]").click()
time.sleep(1.5)
driver.find_element_by_xpath("//input[#type='submit' and #value='Continue']").click()
print('Your order has been placed!')
else:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
print('Your order time will be held for the next hour. Check your date and confirm!')

Related

Not printing the statement after the if loop

from bs4 import BeautifulSoup
import numpy as np
import requests
from selenium import webdriver
from nltk.tokenize import sent_tokenize,word_tokenize
print('ansife')
# html = requests.get('https://www.opentable.com/new-york-restaurant-listings')
# driver = webdriver.Firefox(,executable_path=r'[Your path]\geckodriver.exe')
html = webdriver.Firefox(executable_path=r'D:\geckodriver.exe')
html.get("https://www.opentable.com/new-york-restaurant-listings")
counter = 0
lists = []
def parser_NYU(html):
global counter,lists
total_hotels_more_than_60_bookings = 0
soup = BeautifulSoup(html.page_source,'lxml')
for i, restroom in enumerate(soup.find_all('div',class_='rest-row-info')):
rest_name = restroom.find('span',class_='rest-row-name-text').text
booking = restroom.find('div',class_='booking') #.text
words = list(word_tokenize(str(booking.text)))
#same day
if int(words[1]) > 100:
print(booking.text)
lists.extend([booking.text])
print('listers',len(lists))
print('this works fine')
print('this works fine')
print('listers',len(lists))
print('unfortunately this not works,why?')
print('unfortunately this not works,why?')
parser_NYU(html)
As you can see my print statements are not working after the if loop.
that is :
print('listers',len(lists))
print('this is not printing') # not printing
print('this is not printing')
what did i messed up here?
what will be the main reason behind breaking the entire function after that if loop?
please help me, and advance thanks!
The problem here is two fault:
The actual issues is that at some point booking.text fails because bs4 does not find a div with class=booking, so it returns None, which does not have the attribute .text, so an excpeion is thorwn. Just check if find returned None:
rest_name = restroom.find('span',class_='rest-row-name-text').text
booking = restroom.find('div',class_='booking')
if booking is None:
continue
words = list(word_tokenize(str(booking.text)))
(best also do that for rest_name)
The reason you are not seeing the error message is because it is hidden behind the wall of prints you are doing in the loop. Those are buffered, and might happen at a latter point, e.g. at application exit. However at application exit, the error message has already been printed (unbufferd) and the prints afterward hide them. Always check for errors.
If you mean to print the statements inside the if-condition (not if-loop) add the statements with proper indentation i.e. under the scope of if-condition.
for i, restroom in enumerate(soup.find_all('div',class_='rest-row-info')):
rest_name = restroom.find('span',class_='rest-row-name-text').text
booking = restroom.find('div',class_='booking') #.text
words = list(word_tokenize(str(booking.text)))
#same day
if int(words[1]) > 100:
#-----------Scope of if-block starts here--------#
print(booking.text)
lists.extend([booking.text])
print('listers',len(lists))
print('this is not printing') # not printing
print('this is not printing')
#-----------Scope of if-block ends here--------#
If you mean to print it inside the for-loop and not if-condition, place the print statements under the scope of for-loop
for i, restroom in enumerate(soup.find_all('div',class_='rest-row-info')):
#-------Scope of for-block starts here--------#
rest_name = restroom.find('span',class_='rest-row-name-text').text
booking = restroom.find('div',class_='booking') #.text
words = list(word_tokenize(str(booking.text)))
#same day
if int(words[1]) > 100:
print(booking.text)
lists.extend([booking.text])
print('listers',len(lists))
print('this is not printing') # not printing
print('this is not printing')
#-------Scope of for-block endshere--------#

Code Processing Too Many Values in PySimpleGUI

I have been making an app which lets the users check-boxes. Depending on what boxes they check it will display different information. I decided to use PySimpleGUI for this project. I made 6 check-boxes and one text input which I want the user to be able to choose between the check-boxes and enter a title of a movie in the text input box. Depending on what check-boxes they select it will display different information based on the movie whose title was entered in the text input.
When I try to process the title value entered in the text input it process all values including the boolean values of the check-boxes. The information my code tries to process is: {0: ;'my input', 'Title': True, 'Year': False...}. I only need to process the my input/the movie title input and not the boolean values of the check-boxes.
Here is an example of my code (for reference I am also using the IMDBPY library to search for movies (which I have made work, the problem is that the id = search[0].movieID line is processing too many values.):
def run_code():
global name
while True:
event, value = window.read()
if event == 'SEARCH':
print(values)
name = str(values)[5:-2]
print('Please wait while your results load...')
search = ia.search_movie(name)
id = search[0].movieID
if values['Title'] == True:
print(movie_title)
I am trying to make my code search for the ID of the film title which would be typed by the user in an input field and than (at the bottom) and print the movie title depending if they have the title checkbox selected. At this point I just get an error saying id = search[0].movieID IndexError: list index out of range) To my understanding id = search[0].movieID is taking too many values (which it is, it is taking in all the values, input and check-boxes) I only want it to take in the text input value.
How should I spread out the values to deal with this issue?
Define keys in your sg elements. Then you can pick from values just the item that you need. For example:
def test():
layout = [[sg.Text('Movie title:'), sg.Input(key='input')],
[sg.Checkbox('Title', key='title'), sg.Checkbox('Reverse title', key='reverse')],
[sg.Button('Search', enable_events=True), sg.Cancel()]
]
window = sg.Window('My Window', layout, finalize=True)
while True:
event, values = window.read()
print(f'event = {event}, values = {values}')
if event in (sg.WINDOW_CLOSED, 'Cancel', 'Exit'):
break
if event == 'Search':
print('Searching now...')
if values['title']:
print(f'Title = {values["input"]}')
if values['reverse']:
print(f'Reverse title = {values["input"][::-1]}')
# any other action as needed
For example, when you check both the checkboxes and click on Search, you'll get
event = Search, values = {'input': 'abcde', 'title': True, 'reverse': True}
Searching now...
Title = abcde
Reverse title = edcba
Now you can be sure that you've got the right title as input by the user.
Except of this, you should check the value you get in search = ia.search_movie(name). For a title not found in the database, you'll probably get None and that's where id = search[0].movieID gets you the IndexError: list index out of range because there is no None[0]. So, you may want to add something like
if search is None:
print('Not found!')
continue

Python3 toggle script on key press stuck

The goal of this script is to make a switch and let the user toggle it ON and OFF with a key press, and when the switch is ON, the script should execute a loop that print a message in the terminal. In another words, The goal is to repeatedly print a message when the switch is ON
Here is what i have tried:
import keyboard
import time
switch = 0 # The switch variable
def check_start():
global start
if keyboard.is_pressed("F5") and start == 0: # If F5 is pressed, turn ON print message loop
switch = 1
print(switch)
time.sleep(0.1) # This is to prevent the user toggling the switch too fast
if keyboard.is_pressed("F5") and start == 1: # If F5 is pressed again, turn OFF print message loop
switch = 0
print(switch)
time.sleep(0.1)
def print_name(): # If the switch is ON, it should print two seperate message with a 10 seconds interval
if start == 1:
print("start")
time.sleep(10)
print("end")
while True:
check_start()
print_name()
Here is the output of the script:
1
start
end
start
end
0
1
start
end
0
Now here's the problem:
The user cannot turn off the switch while the print loop is in progress. For example the user cannot turn off the script if the message "end" has not printed, the user can ONLY turn off the switch exactly after "end" has printed, and if the user has missed the opportunity to turn off the switch, he must wait 10 more seconds to turn it off. What i expected is the user can toggle On and OFF anytime s/he wishes to.
Is this possible to do in without importing too much module?
you can put an if condition inside your loop to do a break at a specific key
ex:
if keyboard.is_pressed("F5") and start == 1:
break
this would exit your infinite loop, although a more elegant code would work like this:
def check_start():
global start
if keyboard.is_pressed("F5") and start == 0:
#DO stuff
return True
if keyboard.is_pressed("F5") and start == 1:
#DO stuff
return False
while check_start():
print_name()
Here is a cleaner, more reliable approach using keyboard's built-in add_hotkey method.
Below is a simple program to toggle printing of the current timestamp.
import keyboard as kb
from datetime import datetime as dt
global print_flag
print_flag = False
def toggle_print():
global print_flag
print_flag = not print_flag
kb.add_hotkey('d', toggle_print)
while True:
timestamp = dt.now()
if print_flag:
print(timestamp)

Menu for terminal - Up - Down - Enter - Using module keyboard

Just started using python one week ago. At the moment I am trying to code a small program class which creates a menu in a terminal. The idea is to go through the menu using up / down keys. You can select a menu item by pressing enter. I control the keys being pressed using the module "keyboard".
In order to use my class, one has to create an object and add menu items by means of the method "add_menu". The latter mentioned method has two arguments, the first one is used for the name of the menu item, the second one is used to hand over a function, which will be called in case enter was pressed.
In order to check if a key was pressed, I use the method keyboard.on_press from the module keyboard. In case a key was pressed, the method keyboard.on_press executes the method handle_menu of the menu object. The handle_menu method uses a list named "controller" in order to organize the selection of a menu item. Basically, it is just a list like [0,0,1,0]. The element being 1 indicates the currently selected menu item.
Now to my problem: My menu has a menu item "Exit". If this is selected and enter is pressed, I want the whole program to stop. Therefore, if exit was pressed the attribute exit is changed from 0 to 1. In the while loop of my program I always check if object.exit is != 1, if not the program should end. Somehow this does not always work. If I scroll down immediately from the beginning, without pressing enter at other menu items, it works. However, if I press enter several times at other menu items and then go to the exit menu item, the program does not end anymore (or only if I press enter for 10-20 times). I have the feeling that the keyboard.on_press method and the while loop are sometimes uncoupled in the background and run asynchronously? I do not really understand what is going on...
import keyboard #Using module keyboard
import os
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def start_function():
print('Start works')
def save_function():
print('Save works')
def option_function():
print('Option works')
class c_menu:
def __init__ (self):
self.exit = 0
self.menu = []
self.functions = []
self.controller = []
def add_menu(self, menu, function):
self.menu.append(menu)
self.functions.append(function)
if len(self.controller) == 0:
self.controller.append(1)
else:
self.controller.append(0)
def start_menu(self):
os.system('cls' if os.name == 'nt' else 'clear')
for menu_item in range(len(self.menu)):
if self.controller[menu_item] == 1:
print(bcolors.WARNING + self.menu[menu_item])
else:
print(bcolors.OKBLUE + self.menu[menu_item])
def handle_menu(self, event):
os.system('cls' if os.name == 'nt' else 'clear')
if event.name == 'down':
if self.controller.index(1) != (len(self.controller) - 1):
self.controller.insert(0,0)
self.controller.pop()
elif event.name == 'up':
if self.controller.index(1) != 0:
self.controller.append(0)
self.controller.pop(0)
for menu_item in range(len(self.menu)): #printing all menu items with the right color
if self.controller[menu_item] == 1:
print(bcolors.WARNING + self.menu[menu_item])
else:
print(bcolors.OKBLUE + self.menu[menu_item])
if event.name == 'enter':
if self.functions[self.controller.index(1)] == 'exit':
self.exit = 1
return
self.functions[self.controller.index(1)]()
main_menu = c_menu()
main_menu.add_menu('Start', start_function)
main_menu.add_menu('Save', save_function)
main_menu.add_menu('Option', option_function)
main_menu.add_menu('Exit', 'exit')
main_menu.start_menu()
keyboard.on_press(main_menu.handle_menu)
while main_menu.exit != 1:
pass
I think I understood the problem. The program is actually ending properly, however, the last "enter" pressed is still in a kind of buffer (or something similar) and after the end of program, the terminal command "python menu.py" is executed again and again (it goes so fast that it looks like the program did not end). Unfortunately, I do not really understand why this is happening.
My solution so far, I use "keyboard.send('ctrl+c')" at the very end of my program (after the while loop). This prevents the terminal to re-execute the command "python menu.py" again.

Python curses handling stdout from another thread

I'm running two threads in my python program, one thread which uses python curses to run a menu system and waits for input, and one thread which does analysis based on menu choices and outputs it's status via the built in print() function. My problem here is that print doesn't play well with curses, as, if curses.echo() is on, then it prints to the line where I am waiting for input, and if curses.noecho() is used, then the output is not displayed at all.
Since I want control over where and when the output is displayed, my solution to this initially was to set window.timeout(1000) and then have the input loop like this:
try:
c = window.getkey()
except:
c = -1 #timeout or error in input
if c == -1:
check_for_input()
elif c == 'KEY_RESIZE':
...
This works quite well to allow me to check for output from stdout every second, and then if need be update the menu, while still allowing user input. The problem that I'm having is that I have no idea how to capture stdout and choose to display it when I need to. Is this at all possible?
So I figured this one out, but as a disclaimer, I have no idea if this is thread safe (no problems thus far though).
It's possible to capture the output of print using the python library io, and more specifically StringIO from that library.
N.B. This is for Python3
Essentially, the solution was to set sys.stdout to an instance of io.StringIO and read from that.
external_output = None
stdout_buff = io.StringIO()
sys.stdout = stdout_buff
stream_pos = 0 # lst read position of the stdout stream.
while True: #input loop
...
if stdout_buff.tell() > stream_pos:
stdout_buff.seek(stream_pos)
external_output = stdout_buff.read()
stream_pos = stdout_buff.tell()
...
Below I've included a short example of the menu system I was using in case the above isn't clear to anyone having this issue, in the hopes that this will clear it up.
Cheers!
Unmodified Version
So the menu's display and event loop used to look a lot like this: (note that this is a simplified version of things and therefore a lot to do with displaying the menu and displaying what a user types has been left out). This basic example displays a menu and allows user to exit the program, enter digits into their selection, or enter their selection, which is then printed out.
import sys
import curses
def menu(stdscr):
# initial startup settings
curses.start_color()
curses.use_default_colors()
stdscr.timeout(1000) #timeout the input loop every 1000 milliseconds
user_selection = ''
# other unrelated initial variables
while True: #display loop
stdscr.clear()
# the following is actually in a function to handle automatically
# taking care of fitting output to the screen and keeping
# track of line numbers, etc. but for demonstration purposes
# I'm using the this
start_y = 0
stdscr.addstr(start_y, 0, 'Menu Options:')
stdscr.addstr(start_y+1, 0, '1) option 1')
stdscr.addstr(start_y+2, 0, '1) option 2')
stdscr.addstr(start_y+3, 0, '1) option 3')
stdscr.addstr(start_y+4, 0, '1) option 4')
while True: #input loop
c = stdscr.getkey()
if c == 'KEY_RESIZE':
handle_window_resize() # handle changing stored widths and height of window
break #break to redraw screen
elif c.isdigit():
# if user typed a digit, add that to the selection string
# users may only select digits as their options
user_selection += c
elif c == '\n':
# user hit enter to submit their selection
if len(user_selection) > 0:
return user_selection
elif c == 'q':
sys.exit()
result = curses.wrapper(menu)
print(result)
In this example the problem still occurs that any output from a thread running simultaneously to this one will be printed at the cursor of stdscr where the program is currently waiting for input from the user.
Modified Version
import sys
import curses
from io import StringIO
def menu(stdscr):
# initial startup settings
curses.start_color()
curses.use_default_colors()
stdscr.timeout(1000) #timeout the input loop every 1000 milliseconds
user_selection = ''
# other unrelated initial variables
# output handling variables
external_output = None # latest output from stdout
external_nlines = 2 # number of lines at top to leave for external output
stdout_buff = StringIO()
sys.stdout = stdout_buff
stream_pos = 0 # lst read position of the stdout stream.
while True: #display loop
stdscr.clear()
# the following is actually in a function to handle automatically
# taking care of fitting output to the screen and keeping
# track of line numbers, etc. but for demonstration purposes
# I'm using the this
if external_output is not None:
stdscr.addstr(0, 0, "stdout: " + external_output)
start_y = external_nlines
stdscr.addstr(start_y, 0, 'Menu Options:')
stdscr.addstr(start_y+1, 0, '1) option 1')
stdscr.addstr(start_y+2, 0, '1) option 2')
stdscr.addstr(start_y+3, 0, '1) option 3')
stdscr.addstr(start_y+4, 0, '1) option 4')
while True: #input loop
try:
c = stdscr.getkey()
except:
c = -1 # 1000ms timeout or error
if c == -1:
if stdout_buff.tell() > stream_pos:
# current stdout_buff pos is greater than last read
# stream position, so there is unread output
stdout_buff.seek(stream_pos)
external_output = stdout_buff.read().strip() #strip whitespace
stream_pos = stdout_buff.tell() #set stream_pos to end of stdout_buff
break #redraw screen with new output
elif c == 'KEY_RESIZE':
handle_window_resize() # handle changing stored widths and height of window
break #break to redraw screen
elif c.isdigit():
# if user typed a digit, add that to the selection string
# users may only select digits as their options
user_selection += c
elif c == '\n':
# user hit enter to submit their selection
if len(user_selection) > 0:
sys.stdout = sys.__stdout__ # reset stdout to normal
return user_selection
elif c == 'q':
sys.stdout = sys.__stdout__ # reset stdout to normal
sys.exit()
result = curses.wrapper(menu)
print(result)

Categories

Resources