mirror of
https://github.com/timberjoegithub/GoogleScrape.git
synced 2026-07-22 00:19:48 +00:00
Refactor: repo hygiene, shared utils, focused tests, and CI improvements
- Remove dead imports/duplication from Google2xls.py and social.py - Centralize shared logic in review_workflow_utils.py - Add focused posting-flow tests in tests/test_social.py (all pass) - Update .gitignore for secrets and local artifacts - Trim requirements.txt to direct deps - Improve CI: compile all scripts, run focused tests - Prep repo for clean commit (no secrets, no local artifacts)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
name: Pylint
|
||||
name: Validate
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -14,10 +14,13 @@ jobs:
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
- name: Install validation dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
pip install python-dateutil selenium
|
||||
- name: Compile core scripts
|
||||
run: |
|
||||
pylint $(git ls-files '*.py') --output=lint.txt || true
|
||||
python -m py_compile Google2xls.py WP2xls.py social.py text.py review_workflow_utils.py
|
||||
- name: Run focused unit tests
|
||||
run: |
|
||||
python -m unittest discover -s tests
|
||||
|
||||
-15
@@ -3,22 +3,7 @@ __pycache__/
|
||||
out.xlsx
|
||||
Output/
|
||||
env.py
|
||||
requirements.txt
|
||||
test.py
|
||||
ThreadsPost.py
|
||||
TikTokPost.py
|
||||
WP2xls.py
|
||||
Xpost.py
|
||||
Instapost.py
|
||||
InstaBot.py
|
||||
Google2xls.py
|
||||
google.py
|
||||
geckodriver.log
|
||||
Facepost.py
|
||||
config/
|
||||
env.py
|
||||
social.py
|
||||
.vscode/
|
||||
googleinfo.py
|
||||
social.examples.py
|
||||
debug.log
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import time
|
||||
import os
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.common.by import By
|
||||
import re
|
||||
import urllib3
|
||||
from urllib.request import urlretrieve
|
||||
from openpyxl import Workbook
|
||||
import pandas as pd
|
||||
from env import URL, DriverLocation
|
||||
from datetime import datetime
|
||||
|
||||
from review_workflow_utils import (
|
||||
build_google_chrome_options,
|
||||
build_review_media_path,
|
||||
count_google_review_pages,
|
||||
normalize_google_media_url,
|
||||
sanitize_review_name,
|
||||
scroll_google_reviews,
|
||||
set_stealth_driver,
|
||||
)
|
||||
|
||||
today = datetime.today().strftime('%Y-%m-%d')
|
||||
|
||||
# Rotate User Agent would be helpful
|
||||
|
||||
def get_data(driver):
|
||||
"""
|
||||
this function get main text, score, name
|
||||
"""
|
||||
print('get data...')
|
||||
# Click on more botton on each text reviews
|
||||
more_elemets = driver.find_elements(By.CSS_SELECTOR, '.w8nwRe.kyuRq')
|
||||
for list_more_element in more_elemets:
|
||||
list_more_element.click()
|
||||
# Find Pictures that have the expansion indicator to see the rest of the pictures under them and click it to expose them all
|
||||
more_pics = driver.find_elements(By.CLASS_NAME, 'Tya61d')
|
||||
for list_more_pics in more_pics:
|
||||
if 'showMorePhotos' in list_more_pics.get_attribute("jsaction") :
|
||||
print('Found extra pics')
|
||||
list_more_pics.click()
|
||||
elements = driver.find_elements(By.CLASS_NAME, 'jftiEf')
|
||||
lst_data = []
|
||||
for data in elements:
|
||||
name = data.find_element(By.CSS_SELECTOR, 'div.d4r55.YJxk2d').text
|
||||
try: address = data.find_element(By.CSS_SELECTOR, 'div.RfnDt.xJVozb').text
|
||||
except: address = 'Unknonwn'
|
||||
print ('Name of location: ',name, ' Address:',address)
|
||||
try: visitdate = data.find_element(By.CSS_SELECTOR, 'span.rsqaWe').text
|
||||
except: visitdate = "Unknown"
|
||||
print('Visited: ',visitdate)
|
||||
try: text = data.find_element(By.CSS_SELECTOR, 'div.MyEned').text
|
||||
except: text = ''
|
||||
try: score = data.find_element(By.CSS_SELECTOR, 'span.kvMYJc').get_attribute("aria-label") #find_element(By.CSS_SELECTOR,'aria-label').text #) ##QA0Szd > div > div > div.w6VYqd > div:nth-child(2) > div > div.e07Vkf.kA9KIf > div > div > div.m6QErb.DxyBCb.kA9KIf.dS8AEf > div.m6QErb > div:nth-child(3) > div:nth-child(2) > div > div:nth-child(4) > div.DU9Pgb > span.kvMYJc
|
||||
except: score = "Unknown"
|
||||
more_specific_pics = data.find_elements(By.CLASS_NAME, 'Tya61d')
|
||||
pics= []
|
||||
pics2 = []
|
||||
# check to see if folder for pictures and videos already exists, if not, create it
|
||||
cleanname = sanitize_review_name(name)
|
||||
if not os.path.exists('./Output/Pics/'+cleanname):
|
||||
os.makedirs('./Output/Pics/'+cleanname)
|
||||
# Walk through all the pictures and videos for a given review
|
||||
for lmpics in more_specific_pics:
|
||||
# Grab URL from style definiton (long multivalue string), and remove the -p-k so that it is full size
|
||||
urlmedia = normalize_google_media_url(lmpics.get_attribute("style"))
|
||||
print ('URL : ',urlmedia)
|
||||
pics.append(urlmedia)
|
||||
# time.sleep(2)
|
||||
# photoindex = str(lmpics.get_attribute("data-photo-index"))
|
||||
# Grab the name of the file and remove all spaces and special charecters to name the folder
|
||||
filename = re.sub( r'[^a-zA-Z0-9]','', str(lmpics.get_attribute("aria-label")))
|
||||
# filename = re.sub( r'[^a-zA-Z0-9]','', filename)
|
||||
|
||||
if lmpics == more_specific_pics[0]:
|
||||
lmpics.click()
|
||||
time.sleep(2)
|
||||
#iframe = driver.find_element(By.TAG_NAME, "iframe")
|
||||
tempdate = str((driver.find_element(By.CLASS_NAME,'mqX5ad')).text).rsplit("-",1)
|
||||
visitdate = re.sub( r'[^a-zA-Z0-9]','',tempdate[1])
|
||||
print ('Visited: ',visitdate)
|
||||
# driver.switch_to.default_content()
|
||||
|
||||
# Check to see if it has a sub div, which represents the label with the video length displayed, this will be done
|
||||
# because videos are represented by pictures in the main dialogue, so we need to click through and grab the video URL
|
||||
if (lmpics.find_elements(By.CSS_SELECTOR,'div.fontLabelMedium.e5A3N')) :
|
||||
ext='.mp4'
|
||||
lmpics.click()
|
||||
time.sleep(2)
|
||||
# After we click the right side is rendered in an inframe, Store iframe web element
|
||||
iframe = driver.find_element(By.TAG_NAME, "iframe")
|
||||
# switch to selected iframe
|
||||
driver.switch_to.frame(iframe)
|
||||
# Now find button and click on button
|
||||
video_elements = driver.find_elements(By.XPATH ,'//video') #.get_attribute('src')
|
||||
urlmedia = str((video_elements[0]).get_attribute("src"))
|
||||
# return back away from iframe
|
||||
driver.switch_to.default_content()
|
||||
else:
|
||||
# The default path if it is not a video link
|
||||
ext='.jpg'
|
||||
# Add the correct extension to the file name
|
||||
filename = filename+ext
|
||||
# Test to see if file already exists, and if it does not grab the media and store it in location folder
|
||||
picsLocalpath = build_review_media_path(name, visitdate, filename)
|
||||
if not os.path.isfile(picsLocalpath):
|
||||
urlretrieve(urlmedia, picsLocalpath)
|
||||
# Store the local path to be used in the excel document
|
||||
pics2.append(picsLocalpath)
|
||||
dictPostComplete= {'google':1,'web':0,'yelp':0,'facebook':0,'xtwitter':0,'Instagram':0,'tiktok':0}
|
||||
lst_data.append([name , text, score,pics,pics2,"GoogleMaps",visitdate,address,dictPostComplete])
|
||||
return lst_data
|
||||
|
||||
# Grab a count of how far we need to scroll
|
||||
def counter(driver):
|
||||
return count_google_review_pages(driver)
|
||||
|
||||
# Do the scrolling
|
||||
def scrolling(driver, review_count):
|
||||
return scroll_google_reviews(driver, review_count, label='scrolling...')
|
||||
|
||||
def write_to_xlsx(data):
|
||||
print('write to excel...')
|
||||
|
||||
cols = ["name", "comment", 'rating','picsURL','picsLocalpath','source','date','address','dictPostComplete']
|
||||
df = pd.DataFrame(data, columns=cols)
|
||||
df.to_excel('./Output/reviews.xlsx')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print('starting...')
|
||||
options = build_google_chrome_options(
|
||||
ignore_certificate_errors=True,
|
||||
ignore_ssl_errors=True,
|
||||
)
|
||||
# Setting the driver path and requesting a page
|
||||
driver = webdriver.Chrome(options=options) # Firefox(options=options)
|
||||
# Changing the property of the navigator value for webdriver to undefined
|
||||
set_stealth_driver(driver)
|
||||
driver.get(URL)
|
||||
time.sleep(5)
|
||||
|
||||
review_count = counter(driver)
|
||||
scrolling(driver, review_count)
|
||||
|
||||
data = get_data(driver)
|
||||
driver.close()
|
||||
|
||||
write_to_xlsx(data)
|
||||
print('Done!')
|
||||
@@ -0,0 +1,283 @@
|
||||
import ast
|
||||
import base64
|
||||
import pandas as pd
|
||||
import requests
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from env import wpAPOurl, xls,user, password,wpAPI
|
||||
import datetime as dt
|
||||
import json
|
||||
import re
|
||||
#import socket
|
||||
import urllib3
|
||||
from datetime import datetime
|
||||
|
||||
from review_workflow_utils import normalize_wordpress_date
|
||||
|
||||
today = datetime.today().strftime('%Y-%m-%d')
|
||||
|
||||
#print(urllib.request.urlopen("https://www.stackoverflow.com").getcode())
|
||||
|
||||
def is_port_open(host, port):
|
||||
# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
# removehttps = re.sub(r'https://','', host)
|
||||
# removetrailing = removehttps.split('/')[0]
|
||||
isWebUp = urllib3.request("GET", host)
|
||||
#sock.close()
|
||||
# result = sock.connect_ex((host, port))
|
||||
if isWebUp.status == 200:
|
||||
return True
|
||||
except Exception as error:
|
||||
print ('Could not open port to website: ', host, type(error))
|
||||
return False
|
||||
|
||||
# def to_json(obj):
|
||||
# return json.dumps(obj, default=lambda obj: obj,__dict__ )
|
||||
|
||||
def connect_and_auth_wp( headers):
|
||||
# connect and auth
|
||||
data_string = f"{user}:{password}"
|
||||
token = base64.b64encode(data_string.encode()).decode("utf-8")
|
||||
headers = {"Authorization": f"Basic {token}"}
|
||||
return (headers)
|
||||
|
||||
def check_media(filename, headers):
|
||||
# Regex gilename to format like in WordPress media name
|
||||
#file_name_minus_extension = re.sub(r'\'|(....$)','', filename, flags=re.IGNORECASE)
|
||||
file_name_minus_extension = filename
|
||||
response = requests.get(wpAPI + "/media?search="+file_name_minus_extension, headers=headers)
|
||||
try:
|
||||
result = response.json()
|
||||
file_id = int(result[0]['id'])
|
||||
link = result[0]['guid']['rendered']
|
||||
return file_id, link
|
||||
except Exception:
|
||||
print(' No existing media with same name in Wordpress media folder: ' + filename)
|
||||
return False, False
|
||||
|
||||
def check_post(postname,postdate, headers):
|
||||
|
||||
response = requests.get(wpAPI+"/posts?search="+postname, headers=headers)
|
||||
try:
|
||||
result = response.json()
|
||||
post_id = int(result[0]['id'])
|
||||
post_date = result[0]['date']
|
||||
if postdate == post_date:
|
||||
return post_id
|
||||
except Exception:
|
||||
print('No existing post with same name: ' + postname)
|
||||
return False
|
||||
def post_to_wp(title, content, headers,date, rating,address, picslist):
|
||||
# post
|
||||
NewPost = False
|
||||
countreview = False
|
||||
addresshtml = re.sub(" ", ".",address)
|
||||
googleadress = r"<a href=https://www.google.com/maps/dir/?api=1&destination="+addresshtml + r">"+address+r"</a>" # https://www.google.com/maps/dir/?api=1&destination=760+West+Genesee+Street+Syracuse+NY+13204
|
||||
contentpics = ''
|
||||
picl = picslist[1:-1]
|
||||
pic2 = picl.replace(",","")#re.sub(r',','',picl) #re.sub( r'[^a-zA-Z0-9]','',tempdate[1])
|
||||
pic3= pic2.replace("'","")
|
||||
# print (pic3)
|
||||
pidchop = pic3.split(" ")
|
||||
linkslist=[]
|
||||
# linkslist.clear
|
||||
print (' Figuring out date of Post : ',title)
|
||||
date = normalize_wordpress_date(date)
|
||||
try:
|
||||
post_id = check_post(title, str(date), headers)
|
||||
except Exception as error:
|
||||
print ('Could not check to see post already exists', type(error).__name__)
|
||||
post_id = False
|
||||
if ( post_id == False):
|
||||
# if ((title) != False):
|
||||
googleadress = r"<a href=https://www.google.com/maps/dir/?api=1&destination="+addresshtml + r">"+address+r"</a>"
|
||||
post_data = {
|
||||
"title": title,
|
||||
# "content": address+'\n\n'+content+'\n'+rating+'\n\n' ,
|
||||
"content": googleadress+'\n\n'+content+'\n'+rating ,
|
||||
"status": "publish", # Set to 'draft' if you want to save as a draft
|
||||
"date": date,
|
||||
# "date": str(newdate)+'T22:00:00',
|
||||
# "author":"joesteele"
|
||||
}
|
||||
try:
|
||||
# response = requests.post(wpAPOurl, json=post_data, headers=headers)
|
||||
# myobj = {'json': post_data, 'headers': headers }
|
||||
# response = requests.post(wpAPOurl, json = myobj)
|
||||
headers2 = headers
|
||||
response = requests.post(wpAPOurl, json = post_data, headers=headers2)
|
||||
if ( response.status_code != 201 ):
|
||||
print ('Error: ',response)
|
||||
# print ('json:'+ post_data+' headers :'+headers)
|
||||
# else:
|
||||
# for loop in linkslist:
|
||||
# print (' Adding ', loop['link'], ' to posting')
|
||||
# try:
|
||||
# contentpics += r'<img src="'+ loop['link'] + r' alt="' + title +r'">' +'\n\n'
|
||||
# except Exception as error:
|
||||
# print("An error occurred:", type(error).__name__) # An error occurred:
|
||||
# post_data = ()
|
||||
# response2 = requests.post(wpAPI+"/media/" + response['id'], json = post_data, headers=headers2)
|
||||
else:
|
||||
NewPost = True
|
||||
post_id_json = response.json()
|
||||
post_id = post_id_json.get('id')
|
||||
print (' New post is has post_id = ',post_id)
|
||||
except Exception as error:
|
||||
print("An error occurred:", type(error).__name__) # An error occurred:
|
||||
postneedsupdate = True
|
||||
else:
|
||||
print (' Post already existed: Post ID : ',post_id)
|
||||
for pic in pidchop:
|
||||
picslice2 = pic.split("/")[-1]
|
||||
picslice = picslice2.split(".")
|
||||
picname = picslice[0]
|
||||
caption =title
|
||||
description = title+"\n"+address
|
||||
print (' Found Picture: ',picname)
|
||||
file_id, link = check_media(picname, headers)
|
||||
# link = linknew['rendered']
|
||||
if (file_id) is False:
|
||||
print (' '+picname+' was not already found in library, adding it')
|
||||
countreview = True
|
||||
image = {
|
||||
"file": open(pic, "rb"),
|
||||
"post": post_id,
|
||||
"caption": caption,
|
||||
"description": description
|
||||
}
|
||||
try:
|
||||
image_response = requests.post(wpAPI + "/media", headers=headers, files=image)
|
||||
except Exception as error:
|
||||
print(" An error uploading picture ' + picname+ ' occurred:", type(error).__name__) # An error occurred:
|
||||
if ( image_response.status_code != 201 ):
|
||||
print (' Error- Image ',picname,' was not successfully uploaded. response: ',image_response)
|
||||
else:
|
||||
PicDict=image_response.json()
|
||||
file_id= PicDict.get('id')
|
||||
link = PicDict.get('guid').get("rendered")
|
||||
print (' ',picname,' was successfully uploaded to website with ID: ',file_id, link)
|
||||
try:
|
||||
linksDict = {'file_id' : file_id , 'link' : link}
|
||||
linkslist.append(linksDict)
|
||||
except Exception as error:
|
||||
print(" An error adding to dictionary " , file_id , link , " occurred:", type(error).__name__) # An error occurred:
|
||||
else:
|
||||
print (' Photo ',picname,' was already in library and added to post with ID: ',file_id,' : ',link)
|
||||
try:
|
||||
image_response = requests.post(wpAPI + "/media/" + str(file_id), headers=headers, data={"post" : post_id})
|
||||
except Exception as error:
|
||||
print (' Error- Image ',picname,' was not attached to post. response: ',image_response)
|
||||
try:
|
||||
post_response = requests.get(wpAPI + "/posts/" + str(post_id), headers=headers)
|
||||
if (link in str(post_response.text)):
|
||||
print (' Image link for ', picname, 'already in content of post: ',post_id, post_response.text, link)
|
||||
else:
|
||||
linkslist.append({'file_id' : file_id , 'link' : link})
|
||||
countreview = True
|
||||
except Exception as error:
|
||||
print(" An error loading the metadata from the post " + post_response.title + ' occurred:", type(error).__name__) # An error occurred')
|
||||
#ratinghtml = post_response.text
|
||||
firstMP4 = True
|
||||
for piclink in linkslist:
|
||||
#for loop in linkslist:
|
||||
print (' Adding ', piclink['link'], ' to posting')
|
||||
try:
|
||||
ext = (piclink['link'].split( '.')[-1] )
|
||||
if (ext == 'mp4'):
|
||||
if (firstMP4):
|
||||
contentpics += '\n' +r'[evp_embed_video url="' + piclink['link'] + r'" autoplay="true"]'
|
||||
firstMP4 = False
|
||||
else:
|
||||
contentpics += '\n' +r'[evp_embed_video url="' + piclink['link'] + r'"]'
|
||||
#[evp_embed_video url="http://example.com/wp-content/uploads/videos/vid1.mp4" autoplay="true"]
|
||||
else:
|
||||
contentpics += '\n '+r'<div class="col-xs-4"><img id="'+str(file_id)+r'"' + r'src="' + piclink['link'] + r'"></div>'
|
||||
# contentpics += '\n '+r'<img src="'+ piclink['link'] + '> \n'
|
||||
#contentpics += r'<img src="'+ piclink['link'] + r' alt="' + title +r'">' +'\n\n'
|
||||
except Exception as error:
|
||||
print("An error occurred:", type(error).__name__) # An error occurred:
|
||||
try:
|
||||
response_piclinks = requests.post(wpAPI+"/posts/"+ str(post_id), data={"content" : googleadress+'\n\n'+content+'\n'+rating + contentpics, "featured_media" : file_id}, headers=headers)
|
||||
except Exception as error:
|
||||
print(" An error writing images to the post " + post_response.title + ' occurred:", type(error).__name__) # An error occurred')
|
||||
return NewPost
|
||||
|
||||
# def add_image(wpAPI,pic, picname,caption, description,headers):
|
||||
# # loop thru
|
||||
# image = {
|
||||
# "file": open(pic, "rb"),
|
||||
# "caption": caption,
|
||||
# "description": description
|
||||
# }
|
||||
# try:
|
||||
# image_response = requests.post(wpAPI + "/media", headers=headers, files=image)
|
||||
# except Exception as error:
|
||||
# print("An error occurred:", type(error).__name__) # An error occurred:
|
||||
# if ( image_response != 201 ):
|
||||
# print (image_response)
|
||||
# # print ('json:'+ post_data+' headers :'+headers)
|
||||
# print ('image response: ',image_response)
|
||||
# return image_response
|
||||
|
||||
# def read_from_xlsx(ws):
|
||||
# print('read from excel...')
|
||||
# return (ws)
|
||||
|
||||
def process_reviews(ws, headers):
|
||||
# Process
|
||||
needreversed = False
|
||||
totalcount = 2
|
||||
count = 0
|
||||
rows = list(ws.iter_rows(min_row=1, max_row=ws.max_row))
|
||||
if needreversed:
|
||||
rows = reversed(rows)
|
||||
for processrow in rows:
|
||||
if (count >= totalcount):
|
||||
print ('Exceeded the number of posts per run, exiting')
|
||||
break
|
||||
# for processrow in (ws.rows):
|
||||
# If reviewxls (comment has content) and (picsURL has connect)
|
||||
if processrow[1].value != "name": # Skip header line of xls sheet
|
||||
print ("Processing : ",processrow[1].value)
|
||||
# ast.literal_eval(deployments) = processrow[9].value
|
||||
writtento = (ast.literal_eval(processrow[9].value))
|
||||
# Check to see if the website has already been written to according to the xls sheet, if it has not... then process
|
||||
if ((writtento["web"]) == 0) and (is_port_open(wpAPI, 443)) and (processrow[2].value != None) :
|
||||
try:
|
||||
NewPost = post_to_wp(processrow[1].value, processrow[2].value, headers ,processrow[7].value, processrow[3].value, processrow[8].value, processrow[5].value)
|
||||
try:
|
||||
writtento["web"] = 1
|
||||
#processrow[9] = writtento[9]
|
||||
# processrow[9].value = '"'+writtento+'"'
|
||||
processrow[9].value = str(writtento)
|
||||
except Exception as error:
|
||||
print("An error occurred writing value Excel file:", type(error).__name__) # An error occurred:
|
||||
print ('Success Posting: '+processrow[1].value)# ',processrow[1].value, processrow[2].value, headers,processrow[7].value, processrow[3].value,processrow[8].value, processrow[5].value, temp3["web"] )
|
||||
if NewPost == True:
|
||||
count +=1
|
||||
try:
|
||||
wb.save(xls)
|
||||
except Exception as error:
|
||||
print("An error occurred writing Excel file:", type(error).__name__) # An error occurred:
|
||||
except:
|
||||
print ('Error writing post : ',processrow[1].value, processrow[2].value, headers,processrow[7].value, processrow[3].value,processrow[8].value, processrow[5].value, writtento["web"] )
|
||||
return (headers)
|
||||
|
||||
if __name__ == "__main__":
|
||||
countreview = False
|
||||
headers = {}
|
||||
wb = load_workbook(filename = xls)
|
||||
ws = wb['Sheet1']
|
||||
print('starting...')
|
||||
# read_from_xlsx(ws)
|
||||
print('Connect and auth to Wordpress')
|
||||
data_string = f"{user}:{password}"
|
||||
token = base64.b64encode(data_string.encode()).decode("utf-8")
|
||||
headers = {"Authorization": f"Basic {token}"}
|
||||
# print ('Headers:',headers)
|
||||
connect_and_auth_wp( headers)
|
||||
print('Processing Reviews')
|
||||
# print ('Headers:',headers)
|
||||
process_reviews(ws, headers)
|
||||
print('Done!')
|
||||
+9
-17
@@ -1,22 +1,14 @@
|
||||
autopep8
|
||||
et-xmlfile
|
||||
numpy
|
||||
openpyxl
|
||||
pandas
|
||||
pycodestyle
|
||||
python-dateutil
|
||||
pytz
|
||||
selenium
|
||||
six
|
||||
toml
|
||||
urllib3
|
||||
aiohttp
|
||||
googlemaps
|
||||
instagrapi
|
||||
jsonpickle
|
||||
tweepy
|
||||
sqlalchemy
|
||||
googlemaps
|
||||
moviepy
|
||||
mysqlclient
|
||||
mysql-connector-python
|
||||
pymysql
|
||||
openpyxl
|
||||
pandas
|
||||
python-dateutil
|
||||
requests
|
||||
selenium
|
||||
sqlalchemy
|
||||
tweepy
|
||||
urllib3
|
||||
@@ -0,0 +1,121 @@
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
GOOGLE_REVIEW_SCROLL_XPATH = (
|
||||
'//*[@id="QA0Szd"]/div/div/div[1]/div[2]/div/div[1]/div/div/div[5]/div[2]'
|
||||
)
|
||||
GOOGLE_REVIEW_SCROLL_SCRIPT = (
|
||||
'document.getElementsByClassName("dS8AEf")[0].scrollTop = '
|
||||
'document.getElementsByClassName("dS8AEf")[0].scrollHeight'
|
||||
)
|
||||
|
||||
|
||||
def build_google_chrome_options(
|
||||
headless=False,
|
||||
log_level=None,
|
||||
ignore_certificate_errors=False,
|
||||
ignore_certificate_errors_spki_list=False,
|
||||
ignore_ssl_errors=False,
|
||||
remote_debugging_pipe=False,
|
||||
):
|
||||
from selenium import webdriver
|
||||
|
||||
options = webdriver.ChromeOptions()
|
||||
if log_level is not None:
|
||||
options.add_argument(f"--log-level={log_level}")
|
||||
if ignore_certificate_errors:
|
||||
options.add_argument("--ignore-certificate-error")
|
||||
if ignore_certificate_errors_spki_list:
|
||||
options.add_argument("--ignore-certificate-errors-spki-list")
|
||||
if ignore_ssl_errors:
|
||||
options.add_argument("--ignore-ssl-errors")
|
||||
if headless:
|
||||
options.add_argument("--headless")
|
||||
options.add_argument("--lang=en-US")
|
||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
if remote_debugging_pipe:
|
||||
options.add_argument("--remote-debugging-pipe")
|
||||
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
options.add_experimental_option("useAutomationExtension", False)
|
||||
return options
|
||||
|
||||
|
||||
def set_stealth_driver(driver):
|
||||
driver.execute_script(
|
||||
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
|
||||
)
|
||||
|
||||
|
||||
def count_google_review_pages(driver):
|
||||
result = driver.find_element("class name", 'Qha3nb').text
|
||||
result = result.replace(',', '')
|
||||
result = result.split(' ')
|
||||
result = result[0].split('\n')
|
||||
return int(result[0]) // 10 + 1
|
||||
|
||||
|
||||
def scroll_google_reviews(driver, review_count, label='google_scroll...', delay_seconds=3):
|
||||
print(label, end="")
|
||||
time.sleep(delay_seconds)
|
||||
scrollable_div = driver.find_element("xpath", GOOGLE_REVIEW_SCROLL_XPATH)
|
||||
google_scroller = None
|
||||
for _ in range(review_count):
|
||||
try:
|
||||
google_scroller = driver.execute_script(
|
||||
GOOGLE_REVIEW_SCROLL_SCRIPT,
|
||||
scrollable_div,
|
||||
)
|
||||
time.sleep(delay_seconds)
|
||||
print('.', end="")
|
||||
except Exception as error:
|
||||
print(f"Error while {label.strip('.')}: {error}")
|
||||
break
|
||||
print('')
|
||||
return google_scroller
|
||||
|
||||
|
||||
def sanitize_review_name(name):
|
||||
return re.sub(r'[^a-zA-Z0-9]', '', name)
|
||||
|
||||
|
||||
def normalize_google_media_url(style_text):
|
||||
return re.sub(r'=\S*-p-k-no', '=-no', (re.findall(r"['\"](.*?)['\"]", style_text))[0])
|
||||
|
||||
|
||||
def build_review_media_path(review_name, visit_date, filename, base_dir="./Output/Pics"):
|
||||
review_dir = Path(base_dir) / sanitize_review_name(review_name) / visit_date
|
||||
review_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(review_dir / filename)
|
||||
|
||||
|
||||
def normalize_wordpress_date(date_text, now=None):
|
||||
reference_time = now or datetime.now()
|
||||
normalized = str(date_text).strip()
|
||||
lowered = normalized.lower()
|
||||
|
||||
if lowered == "a day ago":
|
||||
publish_at = reference_time - timedelta(days=1)
|
||||
elif "day" in lowered:
|
||||
publish_at = reference_time - timedelta(days=int(re.sub(r'[^0-9]', '', lowered) or 0))
|
||||
elif lowered == "a week ago":
|
||||
publish_at = reference_time - relativedelta(weeks=1)
|
||||
elif "week" in lowered:
|
||||
publish_at = reference_time - relativedelta(weeks=int(re.sub(r'[^0-9]', '', lowered) or 0))
|
||||
elif lowered == "a month ago":
|
||||
publish_at = reference_time - relativedelta(months=1)
|
||||
elif "month" in lowered:
|
||||
publish_at = reference_time - relativedelta(months=int(re.sub(r'[^0-9]', '', lowered) or 0))
|
||||
elif lowered == "a year ago":
|
||||
publish_at = reference_time - relativedelta(years=1)
|
||||
elif "year" in lowered:
|
||||
publish_at = reference_time - relativedelta(years=int(re.sub(r'[^0-9]', '', lowered) or 0))
|
||||
else:
|
||||
compact_date = normalized.replace(" ", "")
|
||||
publish_at = datetime.strptime(compact_date[:3] + "/" + compact_date[3:] + "/01", '%b/%Y/%d')
|
||||
|
||||
return publish_at.strftime('%Y-%m-%dT22:00:00')
|
||||
@@ -31,6 +31,15 @@ from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import null, update
|
||||
import googlemaps
|
||||
import env
|
||||
from review_workflow_utils import (
|
||||
build_google_chrome_options,
|
||||
build_review_media_path,
|
||||
count_google_review_pages,
|
||||
normalize_google_media_url,
|
||||
sanitize_review_name,
|
||||
scroll_google_reviews,
|
||||
set_stealth_driver,
|
||||
)
|
||||
#import inspect
|
||||
Base = declarative_base()
|
||||
|
||||
@@ -146,8 +155,9 @@ def get_auth_connect():
|
||||
Session = sqlalchemy.orm.sessionmaker()
|
||||
Session.configure(bind=engine)
|
||||
session = Session()
|
||||
usersession = session.query(Users).filter(Users.user=='joesteele')
|
||||
dbuser = usersession[0]
|
||||
dbuser = session.query(Users).filter(Users.user=='joesteele').first()
|
||||
if dbuser is None:
|
||||
raise ValueError("Missing userConfig row for joesteele")
|
||||
#for userloop in usersession:
|
||||
print("- " + dbuser.user + ' ' + dbuser.googleurl)
|
||||
connections['user'] =dbuser
|
||||
@@ -163,7 +173,7 @@ def get_auth_connect():
|
||||
wb = load_workbook(filename = './GoogleScrape/'+ env.xls)
|
||||
xls_wb_df = pd.read_excel('./GoogleScrape/'+ env.xls)
|
||||
else:
|
||||
input("Not able to find xls file Press any key to continue...")
|
||||
raise FileNotFoundError(f"Unable to find xls file: {env.xls}")
|
||||
ws = wb['Sheet1']
|
||||
#xls_wb_df = pd.read_excel(xls)
|
||||
connections |= {'xlsdf':xls_wb_df,'data':ws,'datawb':wb}
|
||||
@@ -175,12 +185,12 @@ def get_auth_connect():
|
||||
c_column = sheet.max_column
|
||||
#out_data=[]
|
||||
#import inspect
|
||||
local_dict=Posts
|
||||
#local_dict=Posts
|
||||
column_list = []
|
||||
my_list = []
|
||||
my_list_data = []
|
||||
my_post = Posts
|
||||
my_dict = {}
|
||||
#my_post = Posts
|
||||
#my_dict = {}
|
||||
for x in inspect.getmembers(Posts):
|
||||
if not (x[0].startswith('_') or 'metadata' in x[0]):
|
||||
column_list.append(x[0])
|
||||
@@ -320,12 +330,7 @@ def counter_google(driver):
|
||||
Returns:
|
||||
int: The total number of search result pages.
|
||||
"""
|
||||
|
||||
result = driver.find_element(By.CLASS_NAME,'Qha3nb').text
|
||||
result = result.replace(',', '')
|
||||
result = result.split(' ')
|
||||
result = result[0].split('\n')
|
||||
return int(result[0]) // 10 + 1
|
||||
return count_google_review_pages(driver)
|
||||
|
||||
##################################################################################################
|
||||
|
||||
@@ -443,7 +448,9 @@ def post_threads_video(group_id, video_path, auth_token, title, content, date, r
|
||||
"""
|
||||
# Get url from wordpress - right now threads can only get video and pics from and external URL
|
||||
|
||||
connect_url = f"https://graph.threads.net/v1.0/{env.threads_page_id}/threads?media_type=VIDEO&video_url=https://www.example.com/images/bronz-fonz.jpg&text=#BronzFonz&access_token=" + auth_token
|
||||
connect_url = f"https://graph.threads.net/v1.0/{env.threads_page_id}/threads?media_type=\
|
||||
VIDEO&video_url=https://www.example.com/images/bronz-fonz.jpg&text=#BronzFonz&\
|
||||
access_token=" + auth_token
|
||||
addresshtml = re.sub(" ", ".",address)
|
||||
files = {eachfile: open(eachfile, 'rb') for eachfile in video_path}
|
||||
data = { "title":title,"description" : title + "\n"+ address+"\nGoogle map to destination: "
|
||||
@@ -503,9 +510,11 @@ def get_google_data(driver, local_outputs):
|
||||
except NoSuchElementException :
|
||||
visitdate = "Unknown"
|
||||
print(' Visited: ',visitdate)
|
||||
if re.match('^[a-zA-Z]+', visitdate) is None and re.match('^[a-zA-Z]+', visitdate) is not None:
|
||||
if re.match('^[a-zA-Z]+', visitdate) is None and re.match('^[a-zA-Z]+', visitdate) is\
|
||||
not None:
|
||||
#if visitdate[:1].isalpha:
|
||||
newdate,newdate2,visitdate = get_wordpress_post_date_string(visitdate, str(datetime.now()))
|
||||
newdate,newdate2,visitdate = get_wordpress_post_date_string(visitdate, \
|
||||
str(datetime.now()))
|
||||
print(' Visited altered to : ',visitdate)
|
||||
try:
|
||||
text = data.find_element(By.CSS_SELECTOR, 'div.MyEned').text
|
||||
@@ -521,9 +530,11 @@ def get_google_data(driver, local_outputs):
|
||||
score = "Unknown"
|
||||
print ('Error: ',error)
|
||||
# Grab more info from google maps entry on this particular review
|
||||
if len(local_outputs['postssession'].query(Posts).filter(Posts.name == name,Posts.google\
|
||||
is not True).all()) == 0 or env.forcegoogleupdate or env.block_google_maps is not\
|
||||
True:
|
||||
needs_google_details = local_outputs['postssession'].query(Posts).filter(
|
||||
Posts.name == name,
|
||||
Posts.google.is_not(True)
|
||||
).first() is not None
|
||||
if needs_google_details or env.forcegoogleupdate or env.block_google_maps is not True:
|
||||
gmaps = googlemaps.Client(env.googleapipass)
|
||||
place_ids = gmaps.find_place(name+address, input_type = 'textquery', fields='')
|
||||
if len(place_ids['candidates']) == 1 :
|
||||
@@ -556,7 +567,7 @@ def get_google_data(driver, local_outputs):
|
||||
more_specific_pics = data.find_elements(By.CLASS_NAME, 'Tya61d')
|
||||
if len(more_specific_pics) > 0:
|
||||
# check to see if folder for pictures and videos already exists, if not, create it
|
||||
cleanname = re.sub( r'[^a-zA-Z0-9]','', name)
|
||||
cleanname = sanitize_review_name(name)
|
||||
if not os.path.exists('./Output/Pics/'+cleanname):
|
||||
os.makedirs('./Output/Pics/'+cleanname)
|
||||
# Walk through all the pictures and videos for a given review
|
||||
@@ -564,8 +575,7 @@ def get_google_data(driver, local_outputs):
|
||||
for lmpics in more_specific_pics:
|
||||
# Grab URL from style definiton (long multivalue string), and remove the -p-k so that
|
||||
# it is full size
|
||||
urlmedia = re.sub(r'=\S*-p-k-no', '=-no', (re.findall(r"['\"](.*?)['\"]",
|
||||
lmpics.get_attribute("style")))[0])
|
||||
urlmedia = normalize_google_media_url(lmpics.get_attribute("style"))
|
||||
print (' Pic URL : ',urlmedia)
|
||||
pics.append(urlmedia)
|
||||
# Grab the name of the file and remove all spaces and special charecters to name the
|
||||
@@ -586,12 +596,13 @@ def get_google_data(driver, local_outputs):
|
||||
ext='.mp4'
|
||||
lmpics.click()
|
||||
time.sleep(2)
|
||||
# After we click the right side is rendered in an inframe, Store iframe web element
|
||||
# After we click the right side is rendered in an inframe,
|
||||
# Store iframe web element
|
||||
iframe = driver.find_element(By.TAG_NAME, "iframe")
|
||||
# switch to selected iframe
|
||||
driver.switch_to.frame(iframe)
|
||||
# Now find button and click on button
|
||||
video_elements = driver.find_elements(By.XPATH ,'//video') #.get_attribute('src')
|
||||
video_elements = driver.find_elements(By.XPATH ,'//video')
|
||||
urlmedia = str((video_elements[0]).get_attribute("src"))
|
||||
current_url = video_elements[0]._parent.current_url
|
||||
# return back away from iframe
|
||||
@@ -600,18 +611,17 @@ def get_google_data(driver, local_outputs):
|
||||
# The default path if it is not a video link
|
||||
ext='.jpg'
|
||||
filename = filename+ext
|
||||
# Test to see if file already exists, and if it does not grab the media and store it
|
||||
# Test to see if file already exists, and if it does not grab the media and store
|
||||
# in location folder
|
||||
if not os.path.exists('./Output/Pics/'+cleanname+'/'+visitdate):
|
||||
os.makedirs('./Output/Pics/'+cleanname+'/'+visitdate)
|
||||
if not os.path.isfile('./Output/Pics/'+cleanname+'/'+visitdate+'/'+filename):
|
||||
urlretrieve(urlmedia, './Output/Pics/'+cleanname+'/'+visitdate+'/'+filename)
|
||||
pics_local_path = build_review_media_path(name, visitdate, filename)
|
||||
if not os.path.isfile(pics_local_path):
|
||||
urlretrieve(urlmedia, pics_local_path)
|
||||
# Store the local path to be used in the excel document
|
||||
pics_local_path = "./Output/Pics/"+cleanname+"/"+visitdate+'/'+filename
|
||||
pics2.append(pics_local_path)
|
||||
if env.forcegoogleupdate:
|
||||
database_update_row(name,"review_id",current_url,"forceall",local_outputs)# Add the correct extension to the file name
|
||||
if len(pics2) > 0 and not os.path.isfile("./Output/Pics/"+cleanname+"/"+visitdate+'/'+cleanname+'-montage.mp4'):
|
||||
database_update_row(name,"review_id",current_url,"forceall",local_outputs)
|
||||
if len(pics2) > 0 and not os.path.isfile("./Output/Pics/"+cleanname+"/"+visitdate+\
|
||||
'/'+cleanname+'-montage.mp4'):
|
||||
make_montage_video_from_google(pics2,cleanname)
|
||||
for loop in pics2:
|
||||
if "montage.mp4" in loop:
|
||||
@@ -644,25 +654,7 @@ def google_scroll(counter_google_scroll,driver):
|
||||
Raises:
|
||||
AttributeError: If an error occurs during scrolling.
|
||||
"""
|
||||
print('google_scroll...', end ="")
|
||||
time.sleep(3)
|
||||
scrollable_div = driver.find_element(By.XPATH,
|
||||
'//*[@id="QA0Szd"]/div/div/div[1]/div[2]/div/div[1]/div/div/div[5]/div[2]')
|
||||
# '//*[@id="QA0Szd"]/div/div/div[1]/div[2]/div/div[1]/div/div/div[2]/div[10]/div')
|
||||
for _i in range(counter_google_scroll):
|
||||
try:
|
||||
google_scroller = driver.execute_script(
|
||||
'document.getElementsByClassName("dS8AEf")[0].\
|
||||
scrollTop=document.getElementsByClassName("dS8AEf")[0].scrollHeight',
|
||||
scrollable_div
|
||||
)
|
||||
time.sleep(3)
|
||||
print('.',end = "")
|
||||
except AttributeError as e:
|
||||
print(f"Error while google_scroll: {e}")
|
||||
break
|
||||
print ('')
|
||||
return google_scroller
|
||||
return scroll_google_reviews(driver, counter_google_scroll, label='google_scroll...')
|
||||
|
||||
##################################################################################################
|
||||
|
||||
@@ -766,9 +758,9 @@ def write_to_database(data, local_outputs):
|
||||
print("Encode Object into JSON formatted Data using jsonpickle")
|
||||
jsonposts = jsonpickle.encode(local_outputs['posts'], unpicklable=False)
|
||||
for processrow in data:
|
||||
getdata = local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0])
|
||||
# getdata = local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0])
|
||||
#if processrow[0] in local_outputs['posts']:
|
||||
if getdata.count() >0:
|
||||
# if getdata.count() >0:
|
||||
# if len(result) > 0:
|
||||
# print (' Row ',processrow[0],' already in database')
|
||||
# d2_row = Posts(name=processrow[0] ,comment=processrow[1],rating=processrow[2]\
|
||||
@@ -781,7 +773,8 @@ def write_to_database(data, local_outputs):
|
||||
# 'dictPostComplete':str(processrow[8])}
|
||||
# if env.forcegoogleupdate:
|
||||
# print (' Row ',processrow[0],' updated in database due to forcegoogleupdate')
|
||||
# local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0]).update\
|
||||
# local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0]).\
|
||||
# update\
|
||||
# (d2_dict)
|
||||
# local_outputs['postssession'].commit()
|
||||
# elif processrow[0] is not None:
|
||||
@@ -797,33 +790,39 @@ def write_to_database(data, local_outputs):
|
||||
# print (' Row ',processrow[0],' added to Database')
|
||||
# # Append the above Python dictionary object as a row to the existing pandas DataFrame
|
||||
# # Using the DataFrame.append() function
|
||||
try:
|
||||
|
||||
getdata2 = local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0])
|
||||
if getdata2.count() >0:
|
||||
print (' Row ',processrow[0],' already in Database')
|
||||
if env.forcegoogleupdate:
|
||||
print (' Row ',processrow[0],' updated in database due to forcegoogleupdate')
|
||||
d2_dict = {'name':processrow[0] ,'comment':processrow[1],'rating':processrow[2]\
|
||||
,'picsURL':str(processrow[3]),'picsLocalpath':str(processrow[4]),\
|
||||
'source':processrow[5],'date':processrow[6],'address':processrow[7],\
|
||||
'dictPostComplete':str(processrow[8])}
|
||||
local_outputs['postssession'].query(Posts).filter(Posts.name == processrow[0]).update\
|
||||
(d2_dict)
|
||||
local_outputs['postssession'].commit()
|
||||
else:
|
||||
d2_row = Posts(name=processrow[0] ,comment=processrow[1],rating=processrow[2]\
|
||||
,picsURL=processrow[3],picsLocalpath=processrow[4],\
|
||||
source=processrow[5],date=processrow[6],address=processrow[7],\
|
||||
dictPostComplete=processrow[8])
|
||||
local_outputs['postssession'].add(d2_row)
|
||||
try:
|
||||
getdata2 = local_outputs['postssession'].query(Posts).filter(Posts.name ==\
|
||||
processrow[0])
|
||||
if getdata2.count() >0:
|
||||
print (' Row ',processrow[0],' already in Database')
|
||||
if env.forcegoogleupdate:
|
||||
print (' Row ',processrow[0],' updated in database due to forcegoogleupdate')
|
||||
d2_dict = {'name':processrow[0] ,'comment':processrow[1],'rating':\
|
||||
processrow[2]\
|
||||
,'picsURL':str(processrow[3]),'picsLocalpath':str(processrow[4]),\
|
||||
'source':processrow[5],'date':processrow[6],'address':processrow[7],\
|
||||
'dictPostComplete':str(processrow[8])}
|
||||
local_outputs['postssession'].query(Posts).filter(Posts.name == \
|
||||
processrow[0]).update(d2_dict)
|
||||
local_outputs['postssession'].commit()
|
||||
print (' Row ',processrow[0],' added to Database')
|
||||
except AttributeError as error:
|
||||
print(' Not able to write to post data table: ' , type(error))
|
||||
local_outputs['postssession'].rollback()
|
||||
raise
|
||||
processrow = []
|
||||
else:
|
||||
# d2_row = Posts(name=processrow[0] ,comment=processrow[1],rating=processrow[2]\
|
||||
# ,picsURL=processrow[3],picsLocalpath=processrow[4],\
|
||||
# source=processrow[5],date=processrow[6],address=processrow[7],\
|
||||
# dictPostComplete=processrow[8])
|
||||
d2_row = {'name':processrow[0] ,'comment':processrow[1],'rating':\
|
||||
processrow[2]\
|
||||
,'picsURL':str(processrow[3]),'picsLocalpath':str(processrow[4]),\
|
||||
'source':processrow[5],'date':processrow[6],'address':processrow[7],\
|
||||
'dictPostComplete':str(processrow[8])}
|
||||
local_outputs['postssession'].add(d2_row)
|
||||
local_outputs['postssession'].commit()
|
||||
print (' Row ',processrow[0],' added to Database')
|
||||
except AttributeError as error:
|
||||
print(' Not able to write to post data table: ' , type(error))
|
||||
local_outputs['postssession'].rollback()
|
||||
raise
|
||||
processrow = []
|
||||
df.to_excel(env.xls)
|
||||
return data
|
||||
|
||||
@@ -972,7 +971,8 @@ def check_wordpress_post(postname, postdate, headers2,local_outputs):
|
||||
print ('Could not query for post on wordpress: ', postname,postdate, type(error))
|
||||
return False, False
|
||||
# try:
|
||||
# newdate,newdate2,visitdate = get_wordpress_post_date_string(result[0]['date'],result[0]['date'])
|
||||
# newdate,newdate2,visitdate = get_wordpress_post_date_string(result[0]['date'],\
|
||||
# result[0]['date'])
|
||||
# newdate3,newdate4,visitdate2 = get_wordpress_post_date_string(postdate,str(datetime.now()))
|
||||
# if visitdate and visitdate2:
|
||||
# print ('Post exists, checking visit date')
|
||||
@@ -1010,7 +1010,8 @@ def check_wordpress_post2(postname, postdate, post_id, headers2,local_outputs):
|
||||
print ('Could not query for post on wordpress: ', postname,postdate, type(error))
|
||||
return False, False
|
||||
# try:
|
||||
# newdate,newdate2,visitdate = get_wordpress_post_date_string(result[0]['date'],result[0]['date'])
|
||||
# newdate,newdate2,visitdate = get_wordpress_post_date_string(result[0]['date'],
|
||||
# result[0]['date'])
|
||||
# newdate3,newdate4,visitdate2 = get_wordpress_post_date_string(postdate,str(datetime.now()))
|
||||
# if visitdate and visitdate2:
|
||||
# print ('Post exists, checking visit date')
|
||||
@@ -2262,21 +2263,12 @@ def process_reviews2(outputs):
|
||||
rows = rows_orig
|
||||
if env.google:
|
||||
print('Configuration says to update google Reviews prior to processing them')
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--log-level=3")
|
||||
#options.add_argument("--ignore-certificate-error")
|
||||
options.add_argument("--ignore-certificate-errors-spki-list")
|
||||
#options.add_argument("--ignore-ssl-errors")
|
||||
if not env.showchrome:
|
||||
options.add_argument("--headless")
|
||||
# show browser or not ||| HEAD => 43.03 ||| No Head => 39 seg
|
||||
options.add_argument("--lang=en-US")
|
||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
options.add_argument("--remote-debugging-pipe")
|
||||
# Exclude the collection of enable-automation switches
|
||||
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
# Turn-off userAutomationExtension
|
||||
options.add_experimental_option("useAutomationExtension", False)
|
||||
options = build_google_chrome_options(
|
||||
headless=not env.showchrome,
|
||||
log_level=3,
|
||||
ignore_certificate_errors_spki_list=True,
|
||||
remote_debugging_pipe=True,
|
||||
)
|
||||
# Setting the driver path and requesting a page
|
||||
caps = webdriver.DesiredCapabilities.CHROME.copy()
|
||||
caps['acceptInsecureCerts'] = True
|
||||
@@ -2289,8 +2281,7 @@ def process_reviews2(outputs):
|
||||
else:
|
||||
driver = webdriver.Chrome(options=options) # Firefox(options=options)
|
||||
# Changing the property of the navigator value for webdriver to undefined
|
||||
driver.execute_script("Object.defineProperty(navigator,'webdriver',\
|
||||
{get:()=> undefined})")
|
||||
set_stealth_driver(driver)
|
||||
driver.get(env.URL)
|
||||
time.sleep(5)
|
||||
google_scroll(counter_google(driver), driver)
|
||||
@@ -2318,7 +2309,7 @@ def process_reviews2(outputs):
|
||||
writtento["threads"]==0 ) and (check_is_port_open(env.wpAPI)) and (env.web \
|
||||
or env.instagram or env.yelp or env.xtwitter or env.tiktok or env.facebook or \
|
||||
env.threads or env.google)and (processrow.comment is not None) :
|
||||
if env.web and processrow.web is False or env.force_web_create is True:
|
||||
if (env.web and processrow.web is False) or env.force_web_create is True:
|
||||
#if writtento["web"] == 0 :
|
||||
try:
|
||||
#date_string = date
|
||||
@@ -2369,16 +2360,29 @@ def process_socials(social_name,social_post,headers,sub_process,social_count, lo
|
||||
Returns:
|
||||
Count of the social that was selected
|
||||
"""
|
||||
post_handler_map = {
|
||||
"web": post_to_wordpress,
|
||||
"instagram": post_to_instagram2,
|
||||
"facebook": post_facebook3,
|
||||
"xtwitter": post_to_x2,
|
||||
"tiktok": post_to_tiktok,
|
||||
}
|
||||
writtento = ast.literal_eval(social_post.dictPostComplete)
|
||||
if (len(local_outputs['postssession'].query(Posts).filter(Posts.name == social_post.name,\
|
||||
getattr(Posts, social_name) is True).all())==0) and ((local_outputs['postssession'].\
|
||||
query(Posts).filter(Posts.name == social_post.name).all()[0].wpurl is not \
|
||||
post_record = local_outputs['postssession'].query(Posts).filter(
|
||||
Posts.name == social_post.name
|
||||
).first()
|
||||
social_column = getattr(Posts, social_name)
|
||||
social_already_written = local_outputs['postssession'].query(Posts).filter(
|
||||
Posts.name == social_post.name,
|
||||
social_column.is_(True)
|
||||
).first()
|
||||
if social_already_written is None and ((post_record is not None and post_record.wpurl is not
|
||||
None)or social_name == 'web'):
|
||||
if social_count < env.postsperrun or (social_name == 'web' and env.force_web_create is \
|
||||
True):
|
||||
try:
|
||||
print(' Starting to generate ',social_name,' post')
|
||||
new_social_post = eval(sub_process)(social_post.name, social_post.comment,\
|
||||
new_social_post = post_handler_map[sub_process](social_post.name, social_post.comment,\
|
||||
headers, social_post.date, social_post.rating, social_post.address,\
|
||||
social_post.picsLocalpath,local_outputs )
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
from review_workflow_utils import (
|
||||
build_review_media_path,
|
||||
count_google_review_pages,
|
||||
normalize_google_media_url,
|
||||
normalize_wordpress_date,
|
||||
sanitize_review_name,
|
||||
)
|
||||
|
||||
|
||||
class FakeElement:
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
|
||||
class FakeDriver:
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def find_element(self, by, value):
|
||||
self.last_lookup = (by, value)
|
||||
return FakeElement(self.text)
|
||||
|
||||
|
||||
class ReviewWorkflowUtilsTests(unittest.TestCase):
|
||||
def test_count_google_review_pages_rounds_up_by_review_page(self):
|
||||
driver = FakeDriver("25 reviews")
|
||||
|
||||
page_count = count_google_review_pages(driver)
|
||||
|
||||
self.assertEqual(page_count, 3)
|
||||
self.assertEqual(driver.last_lookup, ("class name", "Qha3nb"))
|
||||
|
||||
def test_normalize_wordpress_date_handles_relative_weeks(self):
|
||||
publish_date = normalize_wordpress_date("2 weeks ago", now=datetime(2026, 5, 27, 9, 30, 0))
|
||||
|
||||
self.assertEqual(publish_date, "2026-05-13T22:00:00")
|
||||
|
||||
def test_normalize_wordpress_date_handles_month_year_strings(self):
|
||||
publish_date = normalize_wordpress_date("Jan 2025")
|
||||
|
||||
self.assertEqual(publish_date, "2025-01-01T22:00:00")
|
||||
|
||||
def test_normalize_google_media_url_strips_thumbnail_suffix(self):
|
||||
style_text = 'background-image: url("https://example.com/photo=s1600-p-k-no");'
|
||||
|
||||
self.assertEqual(
|
||||
normalize_google_media_url(style_text),
|
||||
"https://example.com/photo=-no",
|
||||
)
|
||||
|
||||
def test_build_review_media_path_sanitizes_review_name(self):
|
||||
media_path = build_review_media_path("Joe's Diner!", "20260527", "front.jpg", base_dir="/tmp/review-tests")
|
||||
|
||||
self.assertEqual(media_path, "/tmp/review-tests/JoesDiner/20260527/front.jpg")
|
||||
self.assertEqual(sanitize_review_name("Joe's Diner!"), "JoesDiner")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,50 @@
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import social
|
||||
|
||||
class SocialPostingFlowTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Patch env and Posts for isolation
|
||||
self.env_patch = patch('social.env')
|
||||
self.mock_env = self.env_patch.start()
|
||||
self.mock_env.request_timeout = 1
|
||||
self.mock_env.facebooksleep = 0
|
||||
self.mock_env.forcegoogleupdate = False
|
||||
self.mock_env.xls = 'dummy.xlsx'
|
||||
self.mock_env.mariadb = False
|
||||
self.mock_env.mariadbuser = 'user'
|
||||
self.mock_env.mariadbpass = 'pass'
|
||||
self.mock_env.mariadbserver = 'localhost'
|
||||
self.mock_env.mariadbdb = 'db'
|
||||
self.mock_env.needreversed = False
|
||||
self.mock_env.postssession = MagicMock()
|
||||
self.mock_env.posts = []
|
||||
self.mock_env.xlsdf = MagicMock()
|
||||
self.mock_env.posts = []
|
||||
self.mock_env.postssession.query.return_value.count.return_value = 0
|
||||
self.addCleanup(self.env_patch.stop)
|
||||
|
||||
@patch('social.requests.post')
|
||||
@patch('builtins.open', new_callable=MagicMock)
|
||||
def test_post_facebook_video_handles_success(self, mock_open, mock_post):
|
||||
mock_post.return_value.json.return_value = {'id': '123'}
|
||||
mock_open.return_value.__enter__.return_value = MagicMock()
|
||||
result = social.post_facebook_video('groupid', ['video.mp4'], 'token', 'title', 'content', 'date', 'rating', 'address')
|
||||
self.assertEqual(result, {'id': '123'})
|
||||
|
||||
@patch('social.requests.post', side_effect=AttributeError('fail'))
|
||||
@patch('builtins.open', new_callable=MagicMock)
|
||||
def test_post_facebook_video_handles_error(self, mock_open, mock_post):
|
||||
mock_open.return_value.__enter__.return_value = MagicMock()
|
||||
result = social.post_facebook_video('groupid', ['video.mp4'], 'token', 'title', 'content', 'date', 'rating', 'address')
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_write_to_database_handles_empty(self):
|
||||
# Should not raise
|
||||
data = []
|
||||
local_outputs = {'xlsdf': MagicMock(), 'posts': [], 'postssession': MagicMock()}
|
||||
result = social.write_to_database(data, local_outputs)
|
||||
self.assertEqual(result, data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user