[CODE] People Also Ask Scraping with Python [Upgraded code from iam_ironman]

I managed to find the issue. I used the following:

import sys
df.to_csv(f'{sys.path[0]}\C:\\Users\\xboxb\\Downloads\\PAA\draft\\{excel}.csv', index=False)

print("\n**********************************")
print(f'saving ({excel}) to csv - finished')
print("**********************************")
 
Yeah, updated the code, now there is an export option as csv - I would recommence json strongly, though, because there might be answers in lists that are nested and csv is allways a bit dirty when it comes to nested data.
Well, if it's a list answer, the string is seperated by TAB ("\t"), though and can be split(python)/explode(php).
Also fixed some bugs in the handleAnswerList() function.

Python:
from seleniumwire import webdriver
import random
import time
##################################################################
#### SETTINGS - The Part, you need to edit
##################################################################
# Your Keyword List
kws = ["bitcoin", "webscraping", "starship enterprise"]
# Limit scraped Questions ||| 0 == All Possible
limit = 15
# Your export File Type  - "json" or "csv", json is recommenced
exportFileType = "csv"
# FileName of Export File
exportFileName = "results"

##################################################################
##################################################################
##################################################################
##### FUNCTIONS
def clickyThingyXpath(xpath, wait, timeout, desc):
    startTime = time.time()
    time.sleep(wait)
    while True:
        checkTime = time.time() - timeout

        if startTime < checkTime:
            return(False)
        try:
            driver.find_element("xpath", xpath).click()
            return(True)
        except:
            time.sleep(1)
            pass

def findTexts(xpath, sleep):
    time.sleep(sleep)
    texts = list()
    _texts = driver.find_elements("xpath",xpath)
    for t in _texts:
        if len(t.text) > 5:
            texts.append(t.text)
    return(texts)

def findQuesionID(questions):
    _id = driver.find_element("xpath", '//*[text() = "'+questions+'"]/parent::*/parent::*/parent::*/parent::*/parent::*').get_attribute("id")
    return(_id)   

def XPathByQuestionID(_id):
    xpath = '//*[@id="'+_id+'"]/div/div/div[1]/div[4]'
    return(xpath)

def XPathByAnswerID(_id):
    clicky = '//*[@id="'+_id+'"]//*[@data-attrid="wa:/description"]'
    return(clicky)

def handleAnswerList(_id):
    _dict = dict()
    _dict["heading"] = findTexts('//*[@id="'+_id+'"]//*[@role="heading"]',0)
    findList = findTexts('//*[@id="'+_id+'"]//li',0)
    if len(findList) > 0:
        _dict["answer"] = findList
        _dict["answerType"] = "list"
        return(_dict)
    else:
        return(None)

def doWhateverYouWandwithThe(results, filetype, filename):
    if filetype == "json":
        import json
        with open(filename+".json", "w") as jsonFile:
            json.dump(results, jsonFile, indent=4)
    if filetype == "csv":
        with open(filename+'.csv','a', encoding='utf-8') as file:
            print(results)
            for kw in results:
                print(kw)
                for question in results[kw]:
                    print(question)
                    if results[kw][question]["answer"]["answerType"] == "text":
                        answer = results[kw][question]["answer"]["answer"][0]
                    if results[kw][question]["answer"]["answerType"] == "list":
                        answer = ""
                        for a in results[kw][question]["answer"]["answer"]:
                            answer = answer + a + "\t"
                        answer.strip()
                    dataPoints = [kw, question, results[kw][question]["question"], results[kw][question]["answer"]["answerType"], answer]
                    for d in dataPoints:
                        file.write(d.replace(";", " -").replace('"', "'").replace("\n", " ").strip()+";")
                    file.write("\n")

##################################################################
##################################################################
##################################################################
##### Script

results = dict()
for kw in kws:
    results[kw] = dict()
    ### Setting Up Webdriver
    ua = ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"]
    options = webdriver.ChromeOptions()
    chrome_prefs = {}
    options.experimental_options["prefs"] = chrome_prefs
    chrome_prefs["profile.default_content_settings"] = {"images": 2}
    chrome_prefs["profile.managed_default_content_settings"] = {"images": 2}
    options.add_argument("--disable-web-security")
    options.add_argument("--disable-site-isolation-trials")
    options.add_argument("--disable-application-cache")
    options.add_experimental_option('excludeSwitches', ['enable-logging'])
    options.add_argument('--user-agent='+random.choice(ua))
    driver = webdriver.Chrome(executable_path=r"chromedriver.exe", chrome_options=options)

    ### Request
    url = 'https://www.google.com/search?q='+kw+'&start=0'
    driver.get(url)

    ### Click on Privacy Stuff - Optional
    clickyThingyXpath("/html/body/div[3]/div[3]/span/div/div/div/div[3]/div[1]/button[1]/div", 1, 60, "Handling Privacy Popup")

    ### Scraping
    seen = list()
    questions = findTexts("//*[@data-q]//div[@role='button']//div/span", 0)
    i = 0
    while True:
        for q in questions:
            try:
                # Preparing result dict
                results[kw][str(i)] = {"question":{}, "answer":{}}
                # check if question done allready, since in loop
                if q in seen:
                    continue
                # Get the question ID for further xpath usage
                qid = findQuesionID(q)
                print(kw+" ||| "+q)
                 # Open question
                clickyThingyXpath(XPathByQuestionID(qid), 0, 10, 'Clicking Question: "'+q+'"')
                # Scrape answer
                answer = findTexts(XPathByAnswerID(qid), 2)
                # check if answer is text only
                if len(answer) == 1:
                    anwerType = "Text"
                    while answer[0] == "":
                        answer = findTexts(XPathByAnswerID(qid), 2)
                    # Add to result dict
                    results[kw][str(i)]["question"] = q
                    results[kw][str(i)]["answer"]["answerType"] = "text"
                    results[kw][str(i)]["answer"]["answer"] = answer
                else:
                    # Not Text, so handle as list
                    answer = handleAnswerList(qid)
                    # Ignore if it's not a List as well
                    if answer == None:
                        seen.append(q)
                        continue
                    # Add to result dict
                    results[kw][str(i)]["question"] = q
                    results[kw][str(i)]["answer"] = answer
                seen.append(q)
                i+=1
                if i == limit:
                    break
            except:
                seen.append(q)
                continue
        if i == limit:
            break
        ### Did more questions pop up?
        _questions = findTexts("//*[@data-q]//div[@role='button']//div/span", 0)
        if len(questions) == len(_questions):
            break
        questions = _questions

    driver.close()

doWhateverYouWandwithThe(results, exportFileType, exportFileName)
 
Has anyone managed to import these in right format for rank math faq plugin? There is 2 sections in the code and in the first one there shouldn't be any line breaks or new lines. I have tried multiple ways to remove those but still few are always left there and it breaks the plugin after importing. I can only make it as headers and paragraphs.
 
Yeah, updated the code, now there is an export option as csv - I would recommence json strongly, though, because there might be answers in lists that are nested and csv is allways a bit dirty when it comes to nested data.
Well, if it's a list answer, the string is seperated by TAB ("\t"), though and can be split(python)/explode(php).
Also fixed some bugs in the handleAnswerList() function.

Python:
from seleniumwire import webdriver
import random
import time
##################################################################
#### SETTINGS - The Part, you need to edit
##################################################################
# Your Keyword List
kws = ["bitcoin", "webscraping", "starship enterprise"]
# Limit scraped Questions ||| 0 == All Possible
limit = 15
# Your export File Type  - "json" or "csv", json is recommenced
exportFileType = "csv"
# FileName of Export File
exportFileName = "results"

##################################################################
##################################################################
##################################################################
##### FUNCTIONS
def clickyThingyXpath(xpath, wait, timeout, desc):
    startTime = time.time()
    time.sleep(wait)
    while True:
        checkTime = time.time() - timeout

        if startTime < checkTime:
            return(False)
        try:
            driver.find_element("xpath", xpath).click()
            return(True)
        except:
            time.sleep(1)
            pass

def findTexts(xpath, sleep):
    time.sleep(sleep)
    texts = list()
    _texts = driver.find_elements("xpath",xpath)
    for t in _texts:
        if len(t.text) > 5:
            texts.append(t.text)
    return(texts)

def findQuesionID(questions):
    _id = driver.find_element("xpath", '//*[text() = "'+questions+'"]/parent::*/parent::*/parent::*/parent::*/parent::*').get_attribute("id")
    return(_id)  

def XPathByQuestionID(_id):
    xpath = '//*[@id="'+_id+'"]/div/div/div[1]/div[4]'
    return(xpath)

def XPathByAnswerID(_id):
    clicky = '//*[@id="'+_id+'"]//*[@data-attrid="wa:/description"]'
    return(clicky)

def handleAnswerList(_id):
    _dict = dict()
    _dict["heading"] = findTexts('//*[@id="'+_id+'"]//*[@role="heading"]',0)
    findList = findTexts('//*[@id="'+_id+'"]//li',0)
    if len(findList) > 0:
        _dict["answer"] = findList
        _dict["answerType"] = "list"
        return(_dict)
    else:
        return(None)

def doWhateverYouWandwithThe(results, filetype, filename):
    if filetype == "json":
        import json
        with open(filename+".json", "w") as jsonFile:
            json.dump(results, jsonFile, indent=4)
    if filetype == "csv":
        with open(filename+'.csv','a', encoding='utf-8') as file:
            print(results)
            for kw in results:
                print(kw)
                for question in results[kw]:
                    print(question)
                    if results[kw][question]["answer"]["answerType"] == "text":
                        answer = results[kw][question]["answer"]["answer"][0]
                    if results[kw][question]["answer"]["answerType"] == "list":
                        answer = ""
                        for a in results[kw][question]["answer"]["answer"]:
                            answer = answer + a + "\t"
                        answer.strip()
                    dataPoints = [kw, question, results[kw][question]["question"], results[kw][question]["answer"]["answerType"], answer]
                    for d in dataPoints:
                        file.write(d.replace(";", " -").replace('"', "'").replace("\n", " ").strip()+";")
                    file.write("\n")

##################################################################
##################################################################
##################################################################
##### Script

results = dict()
for kw in kws:
    results[kw] = dict()
    ### Setting Up Webdriver
    ua = ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"]
    options = webdriver.ChromeOptions()
    chrome_prefs = {}
    options.experimental_options["prefs"] = chrome_prefs
    chrome_prefs["profile.default_content_settings"] = {"images": 2}
    chrome_prefs["profile.managed_default_content_settings"] = {"images": 2}
    options.add_argument("--disable-web-security")
    options.add_argument("--disable-site-isolation-trials")
    options.add_argument("--disable-application-cache")
    options.add_experimental_option('excludeSwitches', ['enable-logging'])
    options.add_argument('--user-agent='+random.choice(ua))
    driver = webdriver.Chrome(executable_path=r"chromedriver.exe", chrome_options=options)

    ### Request
    url = 'https://www.google.com/search?q='+kw+'&start=0'
    driver.get(url)

    ### Click on Privacy Stuff - Optional
    clickyThingyXpath("/html/body/div[3]/div[3]/span/div/div/div/div[3]/div[1]/button[1]/div", 1, 60, "Handling Privacy Popup")

    ### Scraping
    seen = list()
    questions = findTexts("//*[@data-q]//div[@role='button']//div/span", 0)
    i = 0
    while True:
        for q in questions:
            try:
                # Preparing result dict
                results[kw][str(i)] = {"question":{}, "answer":{}}
                # check if question done allready, since in loop
                if q in seen:
                    continue
                # Get the question ID for further xpath usage
                qid = findQuesionID(q)
                print(kw+" ||| "+q)
                 # Open question
                clickyThingyXpath(XPathByQuestionID(qid), 0, 10, 'Clicking Question: "'+q+'"')
                # Scrape answer
                answer = findTexts(XPathByAnswerID(qid), 2)
                # check if answer is text only
                if len(answer) == 1:
                    anwerType = "Text"
                    while answer[0] == "":
                        answer = findTexts(XPathByAnswerID(qid), 2)
                    # Add to result dict
                    results[kw][str(i)]["question"] = q
                    results[kw][str(i)]["answer"]["answerType"] = "text"
                    results[kw][str(i)]["answer"]["answer"] = answer
                else:
                    # Not Text, so handle as list
                    answer = handleAnswerList(qid)
                    # Ignore if it's not a List as well
                    if answer == None:
                        seen.append(q)
                        continue
                    # Add to result dict
                    results[kw][str(i)]["question"] = q
                    results[kw][str(i)]["answer"] = answer
                seen.append(q)
                i+=1
                if i == limit:
                    break
            except:
                seen.append(q)
                continue
        if i == limit:
            break
        ### Did more questions pop up?
        _questions = findTexts("//*[@data-q]//div[@role='button']//div/span", 0)
        if len(questions) == len(_questions):
            break
        questions = _questions

    driver.close()

doWhateverYouWandwithThe(results, exportFileType, exportFileName)
It wasn't working because the proxy comment didn't have the ##.

And yes, you are right. Results are not good like JSON.

But the problem is how do we upload this JSON file on WordPress as a post?
 
First of all, thanks a lot for the script. It is working like a charm.

I tried to increase it's speed using multithreading but was unable to do so. The script just doesn't run.

Have you got any idea how can we speed up selenium?
Thanks
 
First of all, thanks a lot for the script. It is working like a charm.

I tried to increase it's speed using multithreading but was unable to do so. The script just doesn't run.

Have you got any idea how can we speed up selenium?
Thanks
I'm not so into it, but have worked with joblib (https://joblib.readthedocs.io/en/latest/) and selenium once, you can give it a try. If it can be run in command-line only, multiprocessing and stuff like this would work as well. I normaly try to switch to requests and hidden apis on that point, if I want to do it on a productional leven - Selenium is mostly a proof of concept method ;)
But as a warning:
I have no idea, how and if (different parallel) proxies would work in such a build, but might be worth a try. Parallizing proxies in requests is no problem after all, but I dont know how chromium handels em
 
another unnecessary tool. You need to answer that questions providing useful information, and many of those questions are meaningless, since they are generated by google, and google lies about it, listing them under “people also asked” title.
 
Smart

Hope Google will not slap all

LET WATCH And See what the Google update brings
 
another unnecessary tool. You need to answer that questions providing useful information, and many of those questions are meaningless, since they are generated by google, and google lies about it, listing them under “people also asked” title.
I would never call scripts - there is no tool - useless, cause they will generate insights an learnigs by the people using em. As said, I wouldn't use my script on a production level as well, and while it might be usefull in combination with other stuff it can. In that state, it's basic scraping. Calling Data useless is not really constructive in any means. Data is allways usefull, if you use it right.
You're right with the last part, though - "People also searched" would be the more usefull name^^
 
Great adjustements , i already built GUI for iam_ironman old script if anyone interested , i can build another GUI interface for this script all credit for ramazan and his work along with iam_ironman

img404.jpg
 
another unnecessary tool. You need to answer that questions providing useful information, and many of those questions are meaningless, since they are generated by google, and google lies about it, listing them under “people also asked” title.
just pass the thread if you find it unnecessary, as you can see a lot of members were searching for this.

Thank you @Bugfisch for sharing this.
 
another unnecessary tool. You need to answer that questions providing useful information, and many of those questions are meaningless, since they are generated by google, and google lies about it, listing them under “people also asked” title.
It's not my fault you don't have the talent to develop this idea. OpenAI gives free credits and the api is very simple to use. But of course you don't have the capacity to do that.

While someone banking hard on this topic, just keep being toxic.
 
Great adjustements , i already built GUI for iam_ironman old script if anyone interested , i can build another GUI interface for this script all credit for ramazan and his work along with iam_ironman

Nice job, cangrats to you and to the other guys. If you can share the source code for this one and maybe for newer version if you build.

Thanks
 
It's not my fault you don't have the talent to develop this idea. OpenAI gives free credits and the api is very simple to use. But of course you don't have the capacity to do that.

While someone banking hard on this topic, just keep being toxic.

Thanks for the work Ramazan, I will give it a try(openai).

It is easy to criticize in a bad way, but hard to do it in a helpful way. Keep going, hope you bank hard too.
 
Well to be honest AI training is quite simple.

There is a book in marketplace session with "for dummies" tutorial.

If you don't have capacity to train it, I am sure there are a lot of devs
here which can be hired for this task.
 
I normaly try to switch to requests and hidden apis on that point, if I want to do it on a productional leven

First of all, thanks for the script you shared.
Can you elaborate what you mean by "hidden apis"? I only know how to call public api, not have clue about hidden apis :(
 
First of all, thanks for the script you shared.
Can you elaborate what you mean by "hidden apis"? I only know how to call public api, not have clue about hidden apis :(

Hidden api's are network requests some page does when you browse or do some actions on page.

They are not intended to be used as API, but using browser network tool we can replicate those requests and use them.
 
Back
Top