<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Python Scripts for SEOs - Daniel Heredia</title>
	<atom:link href="https://www.danielherediamejias.com/python-scripts-seo/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.danielherediamejias.com</link>
	<description>Digital Marketing &#38; SEO</description>
	<lastBuildDate>Wed, 16 Feb 2022 15:00:13 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.9.1</generator>

<image>
	<url>https://www.danielherediamejias.com/wp-content/uploads/2020/01/cropped-Captura-de-pantalla-2020-01-04-a-las-2.05.48-32x32.png</url>
	<title>Python Scripts for SEOs - Daniel Heredia</title>
	<link>https://www.danielherediamejias.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Find your main relevant words with TF IDF and Python</title>
		<link>https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=find-your-main-relevant-words-with-tf-idf-and-python</link>
					<comments>https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/#comments</comments>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Mon, 22 Nov 2021 16:28:00 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1286</guid>

					<description><![CDATA[<p>Since John Muller confirmed that bold text has some SEO benefits for SEO to help Google to understand the page better there have been quite a lot of discussions about it so on this post I am going to show you how you can use a TF IDF model to find the main words from a group of pages and/or articles so that you can bold them. But first of all, let&#8217;s try to clarify a bit what the TF IDF logic is and how the model works. In short, our TF IDF model will replace the words of the&#8230; <span><a href="https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/">Find your main relevant words with TF IDF and Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Since John Muller confirmed that bold text has some SEO benefits for SEO to help Google to understand the page better there have been quite a lot of discussions about it so on this post I am going to show you how you can use a TF IDF model to find the main words from a group of pages and/or articles so that you can bold them.</p>



<p>But first of all, let&#8217;s try to clarify a bit what the TF IDF logic is and how the model works. In short, our TF IDF model will replace the words of the article with identifiers and it will give a higher score to those terms that appear in our page but not in the other articles. With this logic, if the sample of documents or articles is large enough, it will surely give a low score to stop words like articles and connectors and highlight the actual principal terms. If you would like to know more about TF IDF and its technicalities you can check the article <a href="https://www.holisticseo.digital/python-seo/tf-idf-analyse/">this article that <meta charset="utf-8">Koray Tuğberk recently posted.</a></p>



<p>So considering how a TF IDF model works, what we are going to do in this article is scraping the content from most of my blog posts, creating our own TF IDF model and obtaining the main keywords to be bolded. Something important to mention, it is that as the &#8220;seed&#8221; articles will al be very specialized in a field (concretely SEO and Python), it will not give a high score to Python terms, which is something positive because it will help to differentiate the articles and highlight the main &#8220;gap&#8221; terms. </p>



<p>Creating our own TF IDF model is a technique that will work very well for already clustered and specialized pieces of content. Otherwise, for example if we had use a generic TF IDF model, it would highlight some terms that are not so natural in the language like Python, but very common among my articles.</p>



<h2>1.- Scraping the content from the pages</h2>



<p>First, we will scrape the &lt;p&gt; content from the pages with <a href="https://pypi.org/project/cloudscraper/">cloudscraper</a> and <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">beautifulsoup</a>, we will process these texts with textblob and we will store the processed texts in a list. In the code below, you would need to insert the pages that you would like to use for your model in a list format:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import cloudscraper
from bs4 import BeautifulSoup
from textblob import TextBlob as tb

list_pages = &#91;&lt;insert your pages in a list format&gt;]

scraper = cloudscraper.create_scraper() 
 
list_content = &#91;]

for x in list_pages:
    content = &quot;&quot;
    html = scraper.get(x)
    soup = BeautifulSoup(html.text)
    
    for y in soup.find_all('p'):
            content = content + &quot; &quot; + y.text.lower()
            
    list_content.append(tb(content))            
    
</pre></div>


<h2>2.- Obtaining the main terms with TF IDF</h2>



<p>Now that we have the content, we can compute the term frequencies, train our model and obtain the main terms for each of the pages. I learnt how to use the <a href="https://stevenloria.com/tf-idf/">TF IDF model with Textblob</a> on this article written by Steven Loria.</p>



<p>First we declare our functions:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import math
from textblob import TextBlob as tb

def tf(word, blob):
    return blob.words.count(word) / len(blob.words)

def n_containing(word, bloblist):
    return sum(1 for blob in bloblist if word in blob.words)

def idf(word, bloblist):
    return math.log(len(bloblist) / (1 + n_containing(word, bloblist)))

def tfidf(word, blob, bloblist):
    return tf(word, blob) * idf(word, bloblist)
</pre></div>


<p>And now, we can iterate over the list with the page contents, obtain the main 5 terms and store them in a list that we will use later to export to Excel:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
list_words_scores = &#91;&#91;&quot;URL&quot;,&quot;Word&quot;,&quot;TF-IDF score&quot;]]
for i, blob in enumerate(list_content):
    scores = {word: tfidf(word, blob, list_content) for word in blob.words}
    sorted_words = sorted(scores.items(), key=lambda x: x&#91;1], reverse=True)
    for word, score in sorted_words&#91;:5]:
        list_words_scores.append(&#91;list_pages&#91;i],word,score])
</pre></div>


<h2>3.- Exporting as an Excel file</h2>



<p>Finally, we can export the results as an Excel file with <a href="https://pandas.pydata.org/">Pandas</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd
 
df = pd.DataFrame(list_words_scores)
df.to_excel('&lt;filename&gt;.xlsx', header=False, index=False)
</pre></div>


<p>It will create an Excel file with three columns for the URL, the word and its TF IDF score (which the closer to 1 it is, the more relevant it will be).</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1.png"><img width="1024" height="491" src="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1-1024x491.png" alt="" class="wp-image-1287" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1-1024x491.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1-300x144.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1-768x368.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-1.png 1074w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<h2>4.- Try the Google Colab notebook</h2>



<p>You can try the <a href="https://colab.research.google.com/drive/1FFuBIpuQ7mpesH3xYsP0xRya8z8oPnK8">Google Colab Notebook to find your most relevant terms over here</a>! You will be prompted to share your group pages with a sitemap and you will also need to grant access to Google Colab to your Drive to be able to export the main terms as an Excel file on Drive.</p>



<p></p>



<p></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/">Find your main relevant words with TF IDF and Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.danielherediamejias.com/find-your-main-relevant-words-with-tf-idf-and-python/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Using MOZ API with Python</title>
		<link>https://www.danielherediamejias.com/using-moz-api-with-python/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=using-moz-api-with-python</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Tue, 09 Nov 2021 17:12:12 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1283</guid>

					<description><![CDATA[<p>In this post I am going to show you how you can extract the page authority and the domain authority among other metrics from MOZ in a bulk mode by using its API. As well as the domain authority, we will also be able to fetch the following metrics with the freemium version: Page metatitle if available. Equity links: meaning those links that pass page rank. Total number of links. URL response code. Page Authority. Domain Authority. Timestamp: when the URL was last crawled. The final output of this script will look like: It is worth mentioning that Moz API&#8230; <span><a href="https://www.danielherediamejias.com/using-moz-api-with-python/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/using-moz-api-with-python/">Using MOZ API with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this post I am going to show you how you can extract the page authority and the domain authority among other metrics from MOZ in a bulk mode by using its API. As well as the domain authority, we will also be able to fetch the following metrics with the freemium version:</p>



<ul><li>Page metatitle if available.</li><li>Equity links: meaning those links that pass page rank.</li><li>Total number of links.</li><li>URL response code.</li><li>Page Authority.</li><li>Domain Authority.</li><li>Timestamp: when the URL was last crawled.</li></ul>



<p></p>



<p>The final output of this script will look like:</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image.png"><img width="650" height="287" src="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image.png" alt="" class="wp-image-1277" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/11/image.png 650w, https://www.danielherediamejias.com/wp-content/uploads/2021/11/image-300x132.png 300w" sizes="(max-width: 650px) 100vw, 650px" /></a></figure></div>



<p>It is worth mentioning that Moz API has up to 2.500 requests for free and you <a href="https://moz.com/products/api">can sign up over here</a> if you do not have an account yet. <a href="https://martechwithme.com/bulk-check-domain-authority-da-websites-python/">On this article</a> written by Yanis Illoul you can find more information about how you can get your key to make use of the API. </p>



<p>Does this sound interesting? Let&#8217;s get started then! </p>



<h2>1.- Installing the library</h2>



<p>First of all you need to run the following command in your terminal to install the library <a href="https://github.com/seomoz/SEOmozAPISamples/blob/master/python/README.md">Mozscape</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
pip install git+https://github.com/seomoz/SEOmozAPISamples.git#egg=mozscape&amp;subdirectory=python
</pre></div>


<p>However, unfortunately I encountered some issues and I did not manage to install the library so what I did is executing the piece of code that defines the Mozscape function before making the request to the API in the notebook. The piece of code that I ran <a href="https://github.com/seomoz/SEOmozAPISamples/blob/master/python/mozscape.py">can be found here</a>.</p>



<h2>2.- Making the request</h2>



<p>Now that we have either installed or defined our function, we can make the request and fetch the metrics from MOZ in bulk mode. Nevertheless, first of all we will import the list of domains that we want to check from an Excel file and we will create a list with them. In order to import the domains from the Excel file I am going to use <a href="https://pandas.pydata.org/">Pandas</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd

df = pd.read_excel ('Domains.xlsx')
names = &#91;&#91;x] for x in df.values.tolist()]
</pre></div>


<p>When the domains are already imported and stored in the list, we can just iterate over the list, make the request to the API endpoint and append the API response with the metrics for each domain. Sometimes the API gets overloaded if many requests are made, so we will introduce a try and except and it will sleep for 10 seconds in case an error is returned by the API. You will need to add your key and Moz account in this piece of code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import time

client = Mozscape('&lt;your-moz-account&gt;', '&lt;your-key&gt;')


for x in names:
    try:
        print(x&#91;0])
        domainAuthority = client.urlMetrics(x&#91;0])
        x.append(domainAuthority)
    except Exception as e:
        print(e)
        time.sleep(10)
        domainAuthority = client.urlMetrics(x&#91;0])
        x.append(domainAuthority)
</pre></div>


<p>Once we have got all the metrics, we will format them and we will export them as an Excel file. We will need to remove some deprecated metrics that are returned by the API with the method pop and we will also adapt the timestamp since the API gives it in unix format.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import datetime

list_values = &#91;list(x&#91;1].values()) for x in names]

for y in list_values:
    y&#91;11] = datetime.datetime.fromtimestamp(y&#91;11]).strftime('%Y-%m-%d %H:%M:%S')
    y.pop(4)
    y.pop(4)
    y.pop(4)
    y.pop(4)
</pre></div>


<p>Finally, we can export it as an Excel file again with Pandas:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd
pd.DataFrame(list_values).to_excel(&quot;MOZ_results.xlsx&quot;, header=&#91;&quot;Title&quot;,&quot;URL&quot;,&quot;Equity Links&quot;,&quot;Links&quot;,&quot;Response Code&quot;,&quot;PA&quot;,&quot;DA&quot;,&quot;Last Crawled&quot;], index=False)
</pre></div>


<p>This will create an Excel file that will look like the one above. That is all folks, I hope that you found this post interesting! </p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/using-moz-api-with-python/">Using MOZ API with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Using Cloudflare Analytics API with Python</title>
		<link>https://www.danielherediamejias.com/using-cloudflare-analytics-api-with-python/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=using-cloudflare-analytics-api-with-python</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Thu, 28 Oct 2021 11:08:36 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1264</guid>

					<description><![CDATA[<p>Cloudflare has become a very interesting tool for SEO in order to improve web performance optimization as it enables you to: Create a cache version of your site that is hosted in the nearest location as possible when a request is made. Through the Cloudflare workers you can also write and execute JS on the Cloudflare networks and they will be able to intercept and modify requests, cache content, combine third-party scripts and more. If you do not know how to set up the Cloudflare workers you can check this article written by JC Chouinard that will walk you through&#8230; <span><a href="https://www.danielherediamejias.com/using-cloudflare-analytics-api-with-python/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/using-cloudflare-analytics-api-with-python/">Using Cloudflare Analytics API with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Cloudflare has become a very interesting tool for SEO in order to improve web performance optimization as it enables you to:</p>



<ul><li>Create a cache version of your site that is hosted in the nearest location as possible when a request is made.</li><li>Through the Cloudflare workers you can also write and execute JS on the Cloudflare networks and they will be able to intercept and modify requests, cache content, combine third-party scripts and more. If you do not know how to set up the Cloudflare workers you can check<a href="https://www.jcchouinard.com/cloudflare-workers-for-pagespeed/"> this article</a> written by JC Chouinard that will walk you through the process.</li><li>Improve the security of your site as it will handle DDoS attacks and avoid aggressive scraping that could bring your site down. </li></ul>



<p></p>



<p>Due to all these advantages the use of Cloudflare is lately skyrocketing and already between 15% and 20% of the sites are using it. </p>



<p>On today&#8217;s post, we are going to learn how to use <a href="https://developers.cloudflare.com/analytics/graphql-api">Graphl Cloudflare Analytics API</a> with Python to get some data that can be very insightful to create a profile of the hits that are being made on Cloudflare. The report that we are going to generate can be done with the freemium version of Cloudflare, but there are other specific reports that require an upgraded version.</p>



<h2>1.- Fetching the account information</h2>



<p>In order to authenticate on Cloudflare API we will need to use two things: the email address that is attached to your Cloudflare account and a API key (I use the Global API key). Initially, we can request the account information so that we can extract from the API some data such as the website Zone ID that later on we will use to extract the Cloudflare data of the hits for a specific site.</p>



<p>The email address and the Global API key are sent with the headers to be able to authenticate successfully. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

headers = {
    'X-Auth-Email': '&lt;your-email-address&gt;',
    'X-Auth-Key': '&lt;Global API key&gt;',
    'Content-Type': 'application/json'
}

response = requests.request(
    'GET',
    'https://api.cloudflare.com/client/v4/zones',
    headers=headers
)


data = response.json()
</pre></div>


<h2>2.- Making the request to the Analytics API </h2>



<p>The request to the Analytics API needs to be made to a different endpoint with a POST http request. The metrics that we are interested in are sent with a GraphQL query. The GraphQL query that we are going to use replaced the deprecated Cloudflare Analytics API and returns the very same output.</p>



<p>We are going to use the report called httpRequests1hGroups, which is supported by the free version of Cloudflare and will return the statistics from the hits that have been made to our site for a maximum time range of 259200 seconds (3 days). It can be interesting to fetch this data and store it somewhere else because it only lets you access to data not older than 262800 seconds (around 3 days).</p>



<p>When making the request, we can select if we would like to receive grouped data for that time range or divided by hours. If you would like to split it by hours, you would need to add the dimension datetime to your query and iterate over the hours when you receive the JSON response object. In our case, for simplicity&#8217;s sake, we are going to make the request with the grouped data.</p>



<p>In the code below, you will need to introduce again your email address and global API key plus the ZONE ID of the site that you would like to extract the data from and the initial date and final date of the time range that you would like to check. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests


headers = {
    'X-Auth-Email': '&lt;your-email-address&gt;',
    'X-Auth-Key': '&lt;GLOBAL API KEY&gt;',
    'Content-Type': 'application/json'
}


data = &quot;&quot;&quot;{
  viewer {
    zones(filter: {zoneTag: &lt;website zone ID&gt;}) {
      httpRequests1hGroups( limit: 100, filter: {datetime_geq: &quot;2021-10-27T22:00:00Z&quot;, datetime_lt: &quot;2021-10-28T20:02:00Z&quot;}) {

        sum {
          browserMap {
            pageViews
            uaBrowserFamily
          }
          bytes
          cachedBytes
          cachedRequests
          contentTypeMap {
            bytes
            requests
            edgeResponseContentTypeName
          }
          clientSSLMap {
            requests
            clientSSLProtocol
          }
          countryMap {
            bytes
            requests
            threats
            clientCountryName
          }
          encryptedBytes
          encryptedRequests
          ipClassMap {
            requests
            ipType
          }
          pageViews
          requests
          responseStatusMap {
            requests
            edgeResponseStatus
          }
          threats
          threatPathingMap {
            requests
            threatPathingName
          }
        }
        uniq {
          uniques
        }
      }
    }
  }
}&quot;&quot;&quot;

response = requests.request(
    'POST',
    'https://api.cloudflare.com/client/v4/graphql',
    headers=headers,
    json={'query': data}
)
</pre></div>


<p>Once the request is made successfully, we can take a look at it and see what data we have got and how we can parse the JSON object that we receive with the response.</p>



<h2>3.- What metrics have we got?</h2>



<p>In the response we can find: the number of pageviews, the number of requests, the number of encrypted bytes, the number of encrypted requests, the number of bytes that have been served, the number of cached bytes that have been served and the number of cached requests. This can give us an idea about how well our cache policy is working and the volume of requests and bytes that are being handled by Cloudflare.</p>



<p>In addition, we can also find a response code map, an IP class map, a country map, a content type map, a browser map and a clients SSL map. Even if most of this data can be found on the actual user interface, it can still be good to query it with Python to store it somewhere else as it is only available for the previous 30 days and do further analyses. </p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8.png"><img width="1024" height="604" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8-1024x604.png" alt="" class="wp-image-1272" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8-1024x604.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8-300x177.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8-768x453.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-8.png 1047w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h3>3.1.- Pageviews, requests, bytes, encryption and cache</h3>



<p>With the keys that can be found below we can get the data for <meta charset="utf-8">the number of pageviews, the number of requests, the number of encrypted bytes, the number of encrypted requests, the number of bytes that have been served, the number of cached bytes that have been served and the number of cached requests.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
pageviews = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;pageViews&quot;]
requests_cf = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;requests&quot;]
encrypted_bytes = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;encryptedBytes&quot;]
encryptes_requests = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;encryptedRequests&quot;]
bytes_cf = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;bytes&quot;]
cached_bytes = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;cachedBytes&quot;]
cached_requests = response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;0]&#91;&quot;sum&quot;]&#91;&quot;cachedRequests&quot;]
</pre></div>


<h3>3.2.- Response Status Map</h3>



<p>With the code below we can extract the different status codes and how many requests each one has gotten. Moreover, with Matplotlib we can plot a bar chart that will display this info in a more visual way.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
response_codes = &#91;str(x&#91;&quot;edgeResponseStatus&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;responseStatusMap&quot;]]
requests = &#91;x&#91;&quot;requests&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;responseStatusMap&quot;]]

for x,y in zip(response_codes,requests):

    label = &quot;{:.2f}&quot;.format(y)
    plt.annotate(label, (x,y), textcoords=&quot;offset points&quot;,  xytext=(0,10), ha='center')

ax.bar(response_codes,requests)
plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-2.png"><img width="520" height="330" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-2.png" alt="" class="wp-image-1265" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-2.png 520w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-2-300x190.png 300w" sizes="(max-width: 520px) 100vw, 520px" /></a></figure></div>



<h3>3.3.- Browser Map</h3>



<p>We can get the browser map and plot a bar chart with this piece of code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
browser = &#91;str(x&#91;&quot;uaBrowserFamily&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;browserMap&quot;]]
pageviews = &#91;x&#91;&quot;pageViews&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;browserMap&quot;]]

for x,y in zip(browser,pageviews):

    label = &quot;{:.2f}&quot;.format(y)
    plt.annotate(label, (x,y), textcoords=&quot;offset points&quot;,  xytext=(0,10), ha='center')

ax.bar(browser,pageviews)
plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-3.png"><img width="520" height="330" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-3.png" alt="" class="wp-image-1266" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-3.png 520w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-3-300x190.png 300w" sizes="(max-width: 520px) 100vw, 520px" /></a></figure></div>



<h3>3.4.- Client SSL Map</h3>



<p>We can get the client SSL Map and plot a bar chart with the code below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
ssl_protocol = &#91;str(x&#91;&quot;clientSSLProtocol&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;clientSSLMap&quot;]]
requests = &#91;x&#91;&quot;requests&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;clientSSLMap&quot;]]

for x,y in zip(ssl_protocol,requests):

    label = &quot;{:.2f}&quot;.format(y)
    plt.annotate(label, (x,y), textcoords=&quot;offset points&quot;,  xytext=(0,10), ha='center')

ax.bar(ssl_protocol,requests)
plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-4.png"><img width="520" height="330" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-4.png" alt="" class="wp-image-1267" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-4.png 520w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-4-300x190.png 300w" sizes="(max-width: 520px) 100vw, 520px" /></a></figure></div>



<h3>3.5.- IP Class Map</h3>



<p>Same as in the previous ones, we can get the IP Class Map and plot a chart:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
type_ip = &#91;str(x&#91;&quot;ipType&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;ipClassMap&quot;]]
requests = &#91;x&#91;&quot;requests&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;ipClassMap&quot;]]

for x,y in zip(type_ip,requests):

    label = &quot;{:.2f}&quot;.format(y)
    plt.annotate(label, (x,y), textcoords=&quot;offset points&quot;,  xytext=(0,10), ha='center')

ax.bar(type_ip,requests)
plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-5.png"><img width="520" height="330" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-5.png" alt="" class="wp-image-1268" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-5.png 520w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-5-300x190.png 300w" sizes="(max-width: 520px) 100vw, 520px" /></a></figure></div>



<h3>3.6.- Country Map</h3>



<p>With the piece of code below we will get the country map data and we will plot a chart with two Y axis, one for the requests and  the other one for bytes.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
country = &#91;str(x&#91;&quot;clientCountryName&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;countryMap&quot;]]
bytes_request = &#91;x&#91;&quot;bytes&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;countryMap&quot;]]
requests = &#91;x&#91;&quot;requests&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;countryMap&quot;]]
ax.set_ylabel(&quot;Bytes&quot;,color=&quot;red&quot;,fontsize=14)


ax.plot(country, bytes_request, color=&quot;red&quot;, marker=&quot;o&quot;)
ax2=ax.twinx()
ax2.plot(country, requests,color=&quot;blue&quot;,marker=&quot;o&quot;)
ax2.set_ylabel(&quot;Requests&quot;,color=&quot;blue&quot;,fontsize=14)


plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-6.png"><img width="529" height="341" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-6.png" alt="" class="wp-image-1269" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-6.png 529w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-6-300x193.png 300w" sizes="(max-width: 529px) 100vw, 529px" /></a></figure></div>



<h3>3.7.- Content Type Map</h3>



<p>Last but not least, we can get the content type data and plot another graph with two Y axis for the number of requests and the number of bytes.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.add_axes(&#91;0,0,1,1])
content_type = &#91;str(x&#91;&quot;edgeResponseContentTypeName&quot;]) for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;contentTypeMap&quot;]]
bytes_request = &#91;x&#91;&quot;bytes&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;contentTypeMap&quot;]]
requests = &#91;x&#91;&quot;requests&quot;] for x in response.json()&#91;&quot;data&quot;]&#91;&quot;viewer&quot;]&#91;&quot;zones&quot;]&#91;0]&#91;&quot;httpRequests1hGroups&quot;]&#91;2]&#91;&quot;sum&quot;]&#91;&quot;contentTypeMap&quot;]]
ax.set_ylabel(&quot;Bytes&quot;,color=&quot;red&quot;,fontsize=14)


ax.plot(content_type, bytes_request, color=&quot;red&quot;, marker=&quot;o&quot;)
ax2=ax.twinx()
ax2.plot(content_type, requests,color=&quot;blue&quot;,marker=&quot;o&quot;)
ax2.set_ylabel(&quot;Requests&quot;,color=&quot;blue&quot;,fontsize=14)


plt.show()
</pre></div>


<div class="wp-block-image"><figure class="aligncenter size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-7.png"><img width="529" height="341" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-7.png" alt="" class="wp-image-1270" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-7.png 529w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-7-300x193.png 300w" sizes="(max-width: 529px) 100vw, 529px" /></a></figure></div>



<p>That is all folks, I hope you found this article interesting to get started with Cloudflare Analytics API and Python!</p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/using-cloudflare-analytics-api-with-python/">Using Cloudflare Analytics API with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Extracting keywords ideas with Python and Keyword Planner API segmented by language</title>
		<link>https://www.danielherediamejias.com/python-keyword-planner-api-by-language/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=python-keyword-planner-api-by-language</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Thu, 21 Oct 2021 15:03:37 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1259</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you how you can use Keyword Planner API and Python to extract keyword ideas segmented by language with a practical case. From my point of view, being able to segment by language when doing a keyword research is a very interesting feature that other tools do not offer which enables you to find potential keyword niches for minority languages in very competitive markets. The process that we will follow is: Importing the seed terms: to be able to get the keyword ideas we need a list of seed terms that we are&#8230; <span><a href="https://www.danielherediamejias.com/python-keyword-planner-api-by-language/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/python-keyword-planner-api-by-language/">Extracting keywords ideas with Python and Keyword Planner API segmented by language</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you how you can use Keyword Planner API and Python to extract keyword ideas segmented by language with a practical case. From my point of view, being able to segment by language when doing a keyword research is a very interesting feature that other tools do not offer which enables you to find potential keyword niches for minority languages in very competitive markets.</p>



<p>The process that we will follow is:</p>



<ol><li><strong>Importing the seed terms</strong>: to be able to get the keyword ideas we need a list of seed terms that we are going to obtain from Github based on their frequency. In this way, we can obtain most of the queries that are done<strong> </strong>in a country for a specific language and create a quite extensive database. However, if you would like to do your keyword research focused on an industry rather than a generic one, then you might need to use a list of more specific terms of that industry.</li><li><strong>Making the requests to the API</strong>: for this practical example we will insert a list of seed terms in Spanish and we will extract the keyword ideas of queries that are done in Spanish in the United States through Keyword Planner API and Python.</li><li><strong>Exporting to Excel file</strong>: finally, we can dump all the data into an Excel file if we would like to do further analyses.</li></ol>



<p></p>



<p>This is just a practical example, but if you would like to find more information about how to set up Keyword Planner API, you can have a read at the <a href="https://www.danielherediamejias.com/python-keyword-planner-google-ads-api/">guide that I published to use Keyword Planner API with Python</a>.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-daniel-heredia wp-block-embed-daniel-heredia"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="MBZ9Jq40m7"><a href="https://www.danielherediamejias.com/python-keyword-planner-google-ads-api/">Generating keyword ideas with Python and Keyword Planner from Google Ads API</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" title="&#8220;Generating keyword ideas with Python and Keyword Planner from Google Ads API&#8221; &#8212; Daniel Heredia" src="https://www.danielherediamejias.com/python-keyword-planner-google-ads-api/embed/#?secret=MBZ9Jq40m7" data-secret="MBZ9Jq40m7" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<h2>1.- Finding and importing the seed terms</h2>



<p>As mentioned before, we will get the list of seed terms from Github, thanks to the amazing work that was done by the contributor <a href="https://github.com/hermitdave">Hermit Daves</a>, who created a <a href="https://github.com/hermitdave/FrequencyWords/tree/master/content/2018">repository with lots of lists of language terms</a> from OpenSubtitles based on their frequency. With <a href="https://pandas.pydata.org/">Pandas</a>, we can fetch txt and csv files directly from Github without having to download them if we use the parameter on &#8220;?raw=true&#8221; at the end of Github&#8217;s URL.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd

url = 'https://github.com/hermitdave/FrequencyWords/blob/master/content/2018/es/es_50k.txt?raw=true'
df = pd.read_csv(url,header=None, sep=&quot; &quot;)
seed_terms = &#91;x for x in df&#91;0]]
</pre></div>


<h2>2.- Making the requests to the API</h2>



<p>In order to make the requests to the API we will iterate over the list of seed terms and we will extract the keyword ideas for each of them. Keep in mind, that before proceeding with this piece of code, you need to configure the Google Ads account and create the YAML file with the credentials as explained in the guide to Keyword Planner API. Also, you will need to check these two pages to get the location and language IDS to localize the keyword research: <a href="https://developers.google.com/adwords/api/docs/appendix/geotargeting">Geotargets</a> and <a href="https://developers.google.com/google-ads/api/reference/data/codes-formats#expandable-7">Language Codes</a>.</p>



<p>In the case of our practical example, the location ID for the USA is 2840 and the Spanish language ID is 1003. The total number of terms from the seed terms list is 50.000, but if we do not need to extract so many keyword ideas, we can limit it. In the case below, I cap it to the first 1.000 terms. At the end of each iteration we let the script sleep for 3 seconds to avoid the API overloading. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from google.ads.googleads.client import GoogleAdsClient
import time

client = GoogleAdsClient.load_from_storage(&quot;&lt;your-yaml-file-name&gt;&quot;)

list_keywords = &#91;]
for x in seed_terms&#91;0:1000]:
    list_keywords = list_keywords + main(client, &quot;&lt;your-client-id&gt;&quot;, &#91;&quot;2840&quot;], &quot;1003&quot;, &#91;x] , None)
    time.sleep(3)

</pre></div>


<h2>3.- Exporting to Excel</h2>



<p>Finally, we can export it as an Excel file with Pandas. The output will contain seven columns with the keyword, the average monthly search, the competition level, the competition index, the searches from the past months, the past months and the queries categorizations (Branded, Non-branded, Cars, Year, etcetera). Especial mentions to <a href="https://twitter.com/alex_papageo">Alex Papageorgiou’s</a> because he taught me how to also extract the keyword categories.</p>



<p>First, we will need to adjust a bit our list with the keywords to be able to export it with Pandas and then we will export it.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
list_to_excel = &#91;]
for x in range(len(list_keywords)):
    list_months = &#91;]
    list_searches = &#91;]
    list_annotations = &#91;]
    for y in list_keywords&#91;x].keyword_idea_metrics.monthly_search_volumes:
        list_months.append(str(y.month)&#91;12::] + &quot; - &quot; + str(y.year))
        list_searches.append(y.monthly_searches)
        
    for y in list_keywords&#91;x].keyword_annotations.concepts:
        list_annotations.append(y.concept_group.name)
        
        
    list_to_excel.append(&#91;list_keywords&#91;x].text, list_keywords&#91;x].keyword_idea_metrics.avg_monthly_searches, str(list_keywords&#91;x].keyword_idea_metrics.competition)&#91;28::], list_keywords&#91;x].keyword_idea_metrics.competition_index, list_searches, list_months, list_annotations ])
    
pd.DataFrame(list_to_excel, columns = &#91;&quot;Keyword&quot;, &quot;Average Searches&quot;, &quot;Competition Level&quot;, &quot;Competition Index&quot;, &quot;Searches Past Months&quot;, &quot;Past Months&quot;, &quot;List Annotations&quot;]).to_excel('output.xlsx', header=True, index=False)
</pre></div>


<p>The final output of this exercise looks like:</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image.png"><img width="1024" height="466" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1024x466.png" alt="" class="wp-image-1260" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1024x466.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-300x136.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-768x349.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image.png 1304w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>Having the option of filtering by category is super convenient as we can spot terms related to your industry quite easily and fast by just filtering the Category column for the categories you are interested in the most. For instance, if I wanted to check queries about language related doubts I would only need to filter by &#8220;Language&#8221;.</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1.png"><img width="1024" height="466" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1-1024x466.png" alt="" class="wp-image-1261" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1-1024x466.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1-300x136.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1-768x349.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/image-1.png 1304w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<p>When analyzing the file, you can notice that in the beginning most of the terms come from articles or connectors terms and the generated keywords are not super meaningful, however, once more specific terms are inputed, it throws much more insightful and meaningful keyword ideas.</p>



<p>That is all folks, I hope that you found this article interesting! </p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/python-keyword-planner-api-by-language/">Extracting keywords ideas with Python and Keyword Planner API segmented by language</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Checking metatitles rewrites with Python and Oxylab&#8217;s API</title>
		<link>https://www.danielherediamejias.com/checking-metatitles-rewrites-python-oxylabs-api/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=checking-metatitles-rewrites-python-oxylabs-api</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Mon, 04 Oct 2021 00:25:06 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1228</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you how you can make use of the service provided by Oxylabs called the Real Time Crawler API and Python to scrape the SERPs, extract the metatitle showing up on the SERPs for a page and compare it with your on-page metatitle and H1 to analyze if Google is rewriting your metatitles. The final output of this script will return an Excel file like the one in the screenshot below (without the conditional formatting): If you are not familiar with Oxylab&#8217;s API, you can have a read at this article where I&#8230; <span><a href="https://www.danielherediamejias.com/checking-metatitles-rewrites-python-oxylabs-api/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/checking-metatitles-rewrites-python-oxylabs-api/">Checking metatitles rewrites with Python and Oxylab&#8217;s API</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you how you can make use of the service provided by Oxylabs called the <a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=284&amp;url_id=9">Real Time Crawler API</a> and Python to scrape the SERPs, extract the metatitle showing up on the SERPs for a page and compare it with your on-page metatitle and H1 to analyze if Google is rewriting your metatitles. The final output of this script will return an Excel file like the one in the screenshot below (without the conditional formatting):</p>



<figure class="wp-block-image size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/10/Captura-de-pantalla-2021-10-04-a-las-2.04.44.png"><img width="997" height="471" src="https://www.danielherediamejias.com/wp-content/uploads/2021/10/Captura-de-pantalla-2021-10-04-a-las-2.04.44.png" alt="" class="wp-image-1229" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/10/Captura-de-pantalla-2021-10-04-a-las-2.04.44.png 997w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/Captura-de-pantalla-2021-10-04-a-las-2.04.44-300x142.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/10/Captura-de-pantalla-2021-10-04-a-las-2.04.44-768x363.png 768w" sizes="(max-width: 997px) 100vw, 997px" /></a></figure>



<p>If you are not familiar with Oxylab&#8217;s API, you can have a read at this article where I explain <a href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">how you can use Oxylab&#8217;s API and Python to scrape the SERPs</a>. You will learn how the API works, what type of data you can obtain from it and how you can get the most out of it for SEO with some practical cases.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-daniel-heredia wp-block-embed-daniel-heredia"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="VqGZaKp6du"><a href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">Scraping the Google SERPs with Python and Oxylabs&#8217; API</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" title="&#8220;Scraping the Google SERPs with Python and Oxylabs&#8217; API&#8221; &#8212; Daniel Heredia" src="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/embed/#?secret=VqGZaKp6du" data-secret="VqGZaKp6du" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Having said this, let&#8217;s get started with the metatitles checker!</p>



<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2>1.- How the script works?</h2>



<p>Essentially, we will use Oxylab&#8217;s API to scrape the SERPs with the command &#8220;site:&#8221; for a list of given URLs and then we will scrape the URLs themselves to extract the on-page metatitles and H1s (as Google is likely to use the H1 when not using the actual metatitle). </p>



<p>It might be interesting to do this exercise with a sitemap of URLs to get to know:</p>



<ul><li>What URLs are not indexed at all and take action on them: in the short term a manual indexation on Google Search Console can be requested. For the long term it is possible that some other actions might be required starting from some sanity checks to make sure that the pages are readable by Googlebot, on-page optimizations, the creation of internal and/or external links to increase its page rank, etcetera.</li><li>What URLs show a different metatitle on the SERPs to the on-page title: we can analyze why Google might be changing the metatitle and what alternative it considers that is better for the user. On my site I noticed that Google was excluding in many cases the final site name &#8220;- Daniel Heredia&#8221; with the intention of shortening them because the metatitles were already quite long. </li><li>What URLs are showing the H1 as a metatitle on the SERPs: in those cases I would recommend to go over the H1s and optimize them if there is any way to make them more appealing for users. It can be an enriching process for some sites that optimized their H1s mainly for search engines including unnatural exact matches based on a H1 pattern but disregarded them from a user experience perspective.  </li></ul>



<p></p>



<p>For the script we will use the libraries:</p>



<ul><li><a href="https://docs.python-requests.org/en/latest/">Requests</a>: to make the request to Oxylab&#8217;s endpoint and scrape the SERPs.</li><li><a href="https://pypi.org/project/cloudscraper/">Cloudscraper</a>: to scrape the URLs. It could also be done with requests but cloudscraper is more reliable with sites that use Cloudflare. On the <a href="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/">guide to SEO on-page scraping with Python</a> I introduced cloudscraper and I explained how to scrape metatitles and H1s alongside the rest of SEO elements that can be valuable for SEO.</li><li><a href="https://pypi.org/project/beautifulsoup4/">BeautifulSoup</a>: to parse the object that we are going to receive from our request with cloudscraper.</li></ul>



<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2>2.- Using the script</h2>



<p>First, we need to import the list of URLs that we are going to check. We can use Requests and BeautifulSoup to extract them easily from a sitemap, although they could be imported from any other data source.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from bs4 import BeautifulSoup
import requests

r = requests.get(&quot;https://www.yoursite.com/sitemap.xml&quot;)
xml = r.text

soup = BeautifulSoup(xml)
urls_list = &#91;x.text for x in soup.find_all(&quot;loc&quot;)]
</pre></div>


<p>After importing the URLs, we only need to run the script to scrape the SERPs with Oxylabs and the actual URLs from the list:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import cloudscraper

list_comparison = &#91;]

for url in urls_list:
    scraper = cloudscraper.create_scraper() 

    indexation = False
    metatitle_coincidence = False
    metatitle_coincidence_h1 = False

     payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'site:' + url,
    'parse':'true' 
    }

    response = requests.request(
        'POST',
        'https://realtime.oxylabs.io/v1/queries',
        auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
        json=payload,
    )
 

    for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
        try:
            if x&#91;&quot;url&quot;].endswith(url):
                indexation = True
                html = scraper.get(url)
                soup = BeautifulSoup(html.text)
                metatitle = (soup.find('title')).get_text()
                h1 = (soup.find('h1')).get_text()

                if x&#91;&quot;title&quot;] == metatitle:
                    metatitle_coincidence = True
                    
                if x&#91;&quot;title&quot;] == h1:
                    metatitle_coincidence_h1 = True
                    

                list_comparison.append(&#91;url,indexation,x&#91;&quot;title&quot;],metatitle,h1,metatitle_coincidence,metatitle_coincidence_h1])

                break
        except:
            pass
        
        
    if indexation == False:
        list_comparison.append(&#91;url,indexation,&quot;&quot;,&quot;&quot;,&quot;&quot;,metatitle_coincidence,metatitle_coincidence_h1])
</pre></div>


<p>This will generate a list that will contain the URLs, their indexation statuses, the SERPs metatitles, the on-page metatitles, the on-page H1s and two boolean variables to indicate if the metatitles from the SERPs are equal to the on-page metatitle and/or the on-page H1s.</p>



<p>We can now export this list with Pandas as an Excel file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd
 
df = pd.DataFrame(list_comparison, columns = &#91;&quot;URL&quot;,&quot;Indexation&quot;, &quot;Metatitle SERPs&quot;, &quot;Metatitle&quot;, &quot;H1&quot;, &quot;Metatitle Coincidence&quot;, &quot;H1 - metatitle Coincidence&quot;])
df.to_excel('&lt;filename&gt;.xlsx', header=True, index=False)
</pre></div>


<p>This will return an Excel file that will look like the one shown in the screenshot at the beginning of the post.</p>



<p>That is all folks, I hope that you found this post interesting!</p>



<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/checking-metatitles-rewrites-python-oxylabs-api/">Checking metatitles rewrites with Python and Oxylab&#8217;s API</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Pruning with Python and htaccess for SEO</title>
		<link>https://www.danielherediamejias.com/pruning-with-python-and-htaccess-for-seo/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pruning-with-python-and-htaccess-for-seo</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Mon, 06 Sep 2021 20:44:34 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1212</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you a very easy trick to create a txt snippet that you can use in your htaccess file to set pages as not indexable based on their performances. The logic that we will use is: We use Screaming Frog connected with Google Search Console API to crawl our website and get those pages that do not turn up on Google Search Console. We export those pages as an Excel file. We run a very simple Python code to create a txt snippet that we can paste in our htaccess file to serve&#8230; <span><a href="https://www.danielherediamejias.com/pruning-with-python-and-htaccess-for-seo/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/pruning-with-python-and-htaccess-for-seo/">Pruning with Python and htaccess for SEO</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you a very easy trick to create a txt snippet that you can use in your htaccess file to set pages as not indexable based on their performances. The logic that we will use is:</p>



<ul><li>We use <a href="https://www.screamingfrog.co.uk/seo-spider/">Screaming Frog</a> connected with Google Search Console API to crawl our website and get those pages that do not turn up on Google Search Console.</li><li>We export those pages as an Excel file.</li><li>We run a very simple Python code to create a txt snippet that we can paste in our htaccess file to serve a noindex tag in the HTTP response to prevent the Googlebot to index (and render in most cases) the underperforming pages.</li></ul>



<p>Does this sound interesting? So let&#8217;s get started then!</p>



<h2>1.- Getting the underperforming pages with Screaming Frog</h2>



<p>The first thing that we need to do is connecting Screaming Frog with our Google Search Console account to be able to obtain a report with those URLs that are found in the crawl but not on GSC&#8217;s data. We can access this feature on the navigational menu under API Access &#8211;&gt; Google Search Console:</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image.png"><img width="1024" height="535" src="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1024x535.png" alt="" class="wp-image-1213" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1024x535.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-300x157.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-768x401.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image.png 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<p>After that, Screaming Frog will prompt us to connect with our Google Search Console account. We just need to click on New account and log into our account. Once we are logged in, we need to select the account that corresponds to the site that we are going to crawl.</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1.png"><img width="1024" height="535" src="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1-1024x535.png" alt="" class="wp-image-1214" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1-1024x535.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1-300x157.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1-768x401.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-1.png 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<p>Another important point is that we can extend the date range of the data in the Date Range tab if needed to have a bigger volume of data and avoid setting to not indexable URLs with no impressions due to seasonal effects.</p>



<p>Once everything is set up, we just need to run the crawl and when it is finished, we will have to export the report called &#8220;No GSC data&#8221; which can be found on the sidebar under section Search Console.</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2.png"><img width="1024" height="535" src="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2-1024x535.png" alt="" class="wp-image-1215" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2-1024x535.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2-300x157.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2-768x401.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-2.png 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<h2>2.- Creating the TXT snippets with Python</h2>



<p><a href="https://yoast.com/x-robots-tag-play/">This article from Yoast inspired me</a> to create these txt snippets to be added on the htaccess file as I thought that with Python we could iterate over the list of pages without GSC data very easily and write an exclusion rule for each of them. However, even if this article can be of inspiration to automate the pruning task, I would recommend to those SEOs without a big technical knowledge to be helped out by a developer as the htaccess is a very sensitive file that can break your site in case something is not inserted correctly, although the logic can still be used. In case you would still like to take some risks, you can test the htaccess file with some htaccess validators as I recommended in this article about <a href="https://www.danielherediamejias.com/redirects-python-htaccess/">page to page redirects with Python and htaccess</a>.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-daniel-heredia wp-block-embed-daniel-heredia"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="cnH3MxWCQe"><a href="https://www.danielherediamejias.com/redirects-python-htaccess/">Page to page redirects with Python and htaccess</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" title="&#8220;Page to page redirects with Python and htaccess&#8221; &#8212; Daniel Heredia" src="https://www.danielherediamejias.com/redirects-python-htaccess/embed/#?secret=cnH3MxWCQe" data-secret="cnH3MxWCQe" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Another important thing to be mentioned when pruning your website based on underperforming pages is that some pages might not be performing well due to some technical issues or other reasons, so before deindexing the URLs take some time to have a look at the type of URLs that you are about to set as non-indexable and identify the problem why they do not rank well. Pruning is mainly recommended on those sites with lots of pages with thin content, so if your pages are not performing well due to thin content and there is no intention of extending their contents, it might be a good decision to exclude them. However, if you believe that the content on your pages have a big quality, it is very likely that there must be other issues or it is just a matter of time that Google values that content if it has been published recently. </p>



<p>After this small disclaimer message, let&#8217;s go over the code. First we import with <a href="https://pandas.pydata.org/">Pandas</a> the document with the URLs without GSC data and we exclude all the URLs that do not have a 200 response code because they are already not indexable.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd

file_name = 'search_console_no_gsc_data.xlsx' 
df = pd.read_excel(file_name)

df_200 = df.loc&#91;df&#91;'Status Code'] == 200]
list_200 = df_200.values.tolist()
</pre></div>


<p>When the data is imported, we can just iterate over the URLs and create the text snippet with a for loop. We will also make use of <a href="https://docs.python.org/3/library/urllib.parse.html">urllib.parse</a> so that we can break down the URLs and get only the relative path, which is what we need to add on the htaccess. The code to create the snippet for Apache servers is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from urllib.parse import urlparse

text = &quot;&quot;
for x in list_200:
    text = text + '&lt;FilesMatch &quot;' + urlparse(x&#91;0]).path&#91;1::] + '&quot;&gt;\nHeader set X-Robots-Tag &quot;noindex&quot;\n&lt;/FilesMatch&gt;\n'
    
</pre></div>


<p>Whereas the piece of code to create this txt snippet for Nginx servers is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
from urllib.parse import urlparse

text = &quot;&quot;
for x in list_200:
    
    text = text + '''
location = ''' + urlparse(x&#91;0]).path&#91;1::] + ''' {
    add_header  X-Robots-Tag &quot;noindex&quot;;
}
    '''
</pre></div>


<p>Finally, in both cases we can export the htaccess snippet with:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
file_ = open(&quot;Prunning.txt&quot;, 'w')
file_.write(text)
file_.close()
</pre></div>


<p>If everything goes well, you will export a txt file that should be pasted on your htaccess file and look like:</p>



<figure class="wp-block-image size-full"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-3.png"><img width="839" height="494" src="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-3.png" alt="" class="wp-image-1216" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-3.png 839w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-3-300x177.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/09/image-3-768x452.png 768w" sizes="(max-width: 839px) 100vw, 839px" /></a></figure>



<p></p>



<p>That is all folks, I hope that you found this article useful! </p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/pruning-with-python-and-htaccess-for-seo/">Pruning with Python and htaccess for SEO</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Google Alerts and Outreach with Python</title>
		<link>https://www.danielherediamejias.com/google-alerts-outreach-python/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=google-alerts-outreach-python</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Mon, 16 Aug 2021 22:38:36 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1205</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you how you can make use of Google Alerts with Python and how you can set up an automated workflow to reach out to some websites that might mention your brand or a term closely related to your business but not linking to your site. Basically, what we are going to do on this post is: Learning how to install the library google-alerts for Python. Setting up some alerts and parsing the RSS feed which is generated with the matches. Downloading the matches as an Excel file. Scraping the URLs and searching&#8230; <span><a href="https://www.danielherediamejias.com/google-alerts-outreach-python/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/google-alerts-outreach-python/">Google Alerts and Outreach with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you how you can make use of Google Alerts with Python and how you can set up an automated workflow to reach out to some websites that might mention your brand or a term closely related to your business but not linking to your site.</p>



<p>Basically, what we are going to do on this post is:</p>



<ul><li>Learning how to install the library <a href="https://pypi.org/project/google-alerts/">google-alerts</a> for Python. </li><li>Setting up some alerts and parsing the RSS feed which is generated with the matches.</li><li>Downloading the matches as an Excel file.</li><li>Scraping the URLs and searching for a contact URL or an email address to make contact with these sites and ask for a link.</li></ul>



<p></p>



<p>Does this automated workflow sound interesting? Let&#8217;s get started then! </p>



<p></p>



<h2>1.- Installing google-alerts for Python</h2>



<p>First of all, we will need to install google-alerts for Python and seed our Google Alerts session. The command that we will need to run on our terminal to install google-alerts is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
pip install google-alerts
</pre></div>


<p>After this, we will need to input our email address and our password by running the command:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
google-alerts setup --email &lt;your-email-addressl&gt; --password '&lt;your-password&gt;'
</pre></div>


<p>Finally to seed the Google Alerts session we will need to download the <a href="https://chromedriver.storage.googleapis.com/index.html?path=84.0.4147.30/">version number 84 of Chrome Driver</a> and <a href="https://google-chrome.en.uptodown.com/mac/download/2377367">the version 84 of Google Chrome</a> (be careful with not replacing the current version of Google Chrome when downloading and installing the version 84). Unfortunately, this needs to be done because this library has not been updated since 2020 and it is not compatible with the new versions of Google Chrome and Chrome Driver. </p>



<p>When both Chrome Driver v84 and Google Chrome v84 have been installed, we can already run the following command to seed our Google Alerts session. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
google-alerts seed --driver /tmp/chromedriver --timeout 60
</pre></div>


<p>This command will open a Selenium webdriver session to log us into Google Alerts.</p>



<p></p>



<h2>2.- Creating our first alert</h2>



<p>Once the session is seeded, we can already use Jupyter notebook and Python to play around. We will first need to authenticate:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from google_alerts import GoogleAlerts

ga = GoogleAlerts('&lt;your_email_address&gt;', '&lt;your password&gt;')
ga.authenticate()
</pre></div>


<p>When the authentication is completed, we can create our first alert. For example for the term Barcelona in Spain:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
ga.create(&quot;Barcelona&quot;, {'delivery': 'RSS', &quot;language&quot;: &quot;es&quot;, 'monitor_match': 'ALL', 'region' : &quot;ES&quot;})
</pre></div>


<p>If the alert is created successfully, then it will return an object specifying the term, the language, the region, the match type and the RSS link for that alert:</p>



<figure class="wp-block-image size-full"><img width="824" height="140" src="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-16-a-las-23.44.45.png" alt="" class="wp-image-1206" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-16-a-las-23.44.45.png 824w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-16-a-las-23.44.45-300x51.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-16-a-las-23.44.45-768x130.png 768w" sizes="(max-width: 824px) 100vw, 824px" /></figure>



<p>Very sadly I have not been able to create an alert which would monitor a term for all the countries because if I leave the language and region arguments empty it sets USA and English as default region and language.</p>



<p>If at some point we lose track of the alerts that are active, we can list them with:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
ga.list()
</pre></div>


<p>And if we would like to delete an alert which is no longer useful or redundant, we can delete it by using the monitor_id and running:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
ga.delete(&quot;monitor_id&quot;)
</pre></div>


<p></p>



<h2>3.- Parsing the RSS feed</h2>



<p>In order to parse the RSS feed we will use <a href="https://docs.python-requests.org/en/master/">requests </a>and <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">beautifulsoup</a> and we will extract the ID, the title, the publication date, the update date, the URL and the abstract for each alert. This data is structured as a XML file. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests
from bs4 import BeautifulSoup as Soup

r = requests.get('&lt;your RSS feed&gt;')
soup = Soup(r.text,'xml')

id_alert = &#91;x.text for x in soup.find_all(&quot;id&quot;)&#91;1:len(soup.find_all(&quot;id&quot;))]]
title_alert = &#91;x.text for x in soup.find_all(&quot;title&quot;)&#91;1:len(soup.find_all(&quot;title&quot;))]]
published_alert = &#91;x.text for x in soup.find_all(&quot;published&quot;)]
update_alert = &#91;x.text for x in soup.find_all(&quot;updated&quot;)&#91;1:len(soup.find_all(&quot;updated&quot;))]]
link_alert = &#91;&#91;x&#91;&quot;href&quot;].split(&quot;url=&quot;)&#91;1].split(&quot;&amp;ct=&quot;)&#91;0]] for x in soup.find_all(&quot;link&quot;)&#91;1:len(soup.find_all(&quot;link&quot;))]]
content_alert = &#91;x.text for x in soup.find_all(&quot;content&quot;)]

compiled_list = &#91;&#91;id_alert&#91;x], title_alert&#91;x], published_alert&#91;x], update_alert&#91;x], link_alert&#91;x], content_alert&#91;x]] for x in range(len(id_alert))]
</pre></div>


<p>With this piece of code we will get an individual list for each metric and a compiled list with all the metrics by alert.</p>



<p>If we would like to, we can download the alerts as an Excel file with <a href="https://pandas.pydata.org/">Pandas</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd
 
df = pd.DataFrame(compiled_list, columns = &#91;&quot;ID&quot;, &quot;Title&quot;, &quot;Published on:&quot;, &quot;Updated on&quot;, &quot;Link&quot;, &quot;Content&quot;])
df.to_excel('new_alerts.xlsx', header=True, index=False)
</pre></div>


<p>This will create an Excel document that will look like:</p>



<figure class="wp-block-image size-large"><a href="https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1.png"><img width="1024" height="360" src="https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1-1024x360.png" alt="" class="wp-image-1207" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1-1024x360.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1-300x105.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1-768x270.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/image-1.png 1364w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p></p>



<h2>4.- Reaching out to the sites</h2>



<p>From my point of view, using Google Alerts with Python can be specially useful when trying to automate a process to reach out to sites when they mention a brand or a specific term that can be closely related to a brand or product. With Python, we can iterate over the list of URLs, scrape them and intend to find a contact page or an email address to contact these sites. In case of finding an email address, even the delivery of an email could also be automated with Python or any other outreach tool. </p>



<p>We can use this piece of code to find those strings that contain &#8220;@&#8221; (very likely email addresses) and contact pages. The filter to leave out some strings that might contain &#8220;@&#8221; but not be an email address can be polished, for now I only excluded those strings which are PNG images:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import re

for iteration in link_alert:
    
    request_link = requests.get(iteration&#91;0])
    soup = Soup(request_link.text,'html')

    body = soup.find(&quot;body&quot;).text
    match = &#91;x for x in re.findall(r'&#91;\w.+-]+@&#91;\w-]+\.&#91;\w.-]+', body) if &quot;.png&quot; not in x]
    
    contact_urls = &#91;]
    links = soup.find_all(&quot;a&quot;)
    for y in links:
        if &quot;contact&quot; in y.text.lower():
            contact_urls.append(y&#91;&quot;href&quot;])
    
    iteration.append(&#91;match])
    iteration.append(&#91;contact_urls])
</pre></div>


<p>Lastly, we can iterate over the list of email addresses and use a piece of code that I published on this article about <a href="https://www.danielherediamejias.com/what-to-do-with-your-outputs-python/">what to do with your outputs when running Python scripts</a>, which uses <a href="https://docs.python.org/3/library/email.encoders.html">email.encoder</a> to send emails with a message like:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.text import MIMEText
import smtplib 
 
#We enter the password, the email adress and the subject for the email
msg = MIMEMultipart()
password = '&lt;your email address password&gt;'
msg&#91;'From'] = &quot;&lt;your email address&gt;&quot;
msg&#91;'To'] = &quot;&lt;Receiver email address&gt;&quot;
 
#Here we set the message. If we send an HTML we can include tags
msg&#91;'Subject'] = &quot;Daniel Heredia - Thank you so much!&quot;
message = &quot;&lt;p&gt;Dear lady or Sir&lt;p&gt;,&lt;br&gt;&lt;br&gt;&lt;p&gt;I would like to thank your for the mention of my brand on your article: &quot; + URL + &quot; and I would like to ask you if it were possible to include a link pointing to my website https://www.danielherediamejias.com to enable those users that are interested in my brand to get to know about me.&lt;/p&gt;&lt;br&gt;&lt;br&gt;&lt;p&gt;Thank you so much in advance!&lt;/p&gt;&quot;
 
#It attaches the message and its format, in this case, HTML
msg.attach(MIMEText(message, 'html'))
 
#It creates the server instance from where the email is sent
server = smtplib.SMTP('smtp.gmail.com: 587')
server.starttls()
 
#Login Credentials for sending the mail
server.login('&lt;your email address&gt;', password)
 
# send the message via the server.
server.sendmail(msg&#91;'From'], msg&#91;'To'], msg.as_string())
server.quit()
</pre></div>


<p>Unluckily I am not a very creative person, so I guess that the message to be sent could be much more appealing! That is all folks, I hope that you found this article interesting! </p>



<p></p>



<p></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/google-alerts-outreach-python/">Google Alerts and Outreach with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>On-page optimization with Python for SEO</title>
		<link>https://www.danielherediamejias.com/onpage-optimization-python-seo/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=onpage-optimization-python-seo</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Tue, 10 Aug 2021 18:23:20 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1191</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you how you can use Python to find terms occurrences to improve your on-page optimization. Basically, what we are going to do is: Using a keyword import from Semrush we will extract keywords and URLs ranking for those keywords. We will iterate over the list of URLs, we will scrape their contents and we will search for the keywords occurrences on metatitles, metadescriptions, H1, H2 and paragraphs. Finally, we will download this data as an Excel file which will have a conditional formatting that will help us to spot some possible optimizations,&#8230; <span><a href="https://www.danielherediamejias.com/onpage-optimization-python-seo/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/onpage-optimization-python-seo/">On-page optimization with Python for SEO</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you how you can use Python to find terms occurrences to improve your on-page optimization. Basically, what we are going to do is:</p>



<ol><li>Using a keyword import from Semrush we will extract keywords and URLs ranking for those keywords.</li><li>We will iterate over the list of URLs, we will scrape their contents and we will search for the keywords occurrences on metatitles, metadescriptions, H1, H2 and paragraphs.</li><li>Finally, we will download this data as an Excel file which will have a conditional formatting that will help us to spot some possible optimizations, working similar to a heatmap.</li><li>From my point of view, the final cherry-picking of optimizations needs to be manually and some of the possible optimizations can be disregarded in order to not overoptimize the page or to keep it natural. </li></ol>



<p>The final Excel file will look like as follows:</p>



<figure class="wp-block-image size-large"><img width="1024" height="415" src="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.30.04-1024x415.png" alt="" class="wp-image-1196" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.30.04-1024x415.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.30.04-300x122.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.30.04-768x311.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.30.04.png 1412w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Does it sound interesting? Let&#8217;s get started then! </p>



<h2 id="1-importing-the-data-from-semrush">1.- Importing the data from Semrush</h2>



<p>Initially, we will need to download a keyword level report from Semrush and import it to our notebook. For that, we will use <a href="https://pandas.pydata.org/">pandas.</a></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import pandas as pd

keywords = pd.read_excel ('&lt;import-file-name&gt;.xlsx')
</pre></div>


<p>Secondly, with the purpose of finding the low hanging fruits and maximize the return of this exercise, we will leave out those keywords which are ranking out of the top 15, although depending on the number of keywords and the current rankings, you can make your threshold higher or lower.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
low_hanging = keywords&#91;keywords&#91;'Position'] &lt; 15]
low_hanging_list = low_hanging.values.tolist()
</pre></div>


<p>To avoid having to crawl an URL several times once we iterate over them, we will adjust the format of our input, transforming the list into a dictionary where the URL will be the key and we will save the keyword, the current ranking and the number of monthly searches as its values.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
dict_urls = {}
for urls in low_hanging_list:
    if urls&#91;7] in dict_urls:
        dict_urls&#91;urls&#91;7]] += &#91;&#91;urls&#91;0],urls&#91;1],urls&#91;3]]]
    else:
        dict_urls&#91;urls&#91;7]] = &#91;&#91;urls&#91;0],urls&#91;1],urls&#91;3]]]
</pre></div>


<p>The format is ready to proceed with the web scraping and finding the term occurrences!</p>



<p></p>



<h2 id="2-scraping-the-urls-and-finding-the-occurrences">2.- Scraping the URLs and finding the occurrences</h2>



<p>To scrape the URLs we will use the Python library called <a href="https://pypi.org/project/cloudscraper/">cloudscraper</a>. As I have already mentioned in other posts, I really like this library, which depends on Requests + requests_toolbelt, as it enables you to scrape those sites which are using Cloudflare without being banned. </p>



<p>In addition, to be able to parse the HTML response we will use <a href="https://pypi.org/project/beautifulsoup4/">beautifulsoup</a>, which will enable us to make the object parsable. If you are interested in web scraping for SEO or other purposes, you can have a read at my previous article where I explained how you can extract all the content from a page based on their selectors.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-daniel-heredia wp-block-embed-daniel-heredia"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="iGz2aRryoy"><a href="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/">Guide to SEO on-page scraping with Python</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" title="&#8220;Guide to SEO on-page scraping with Python&#8221; &#8212; Daniel Heredia" src="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/embed/#?secret=iGz2aRryoy" data-secret="iGz2aRryoy" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>Once we search for the term occurrences, we will aim at a broad match, meaning that we will check if the terms are present in the content separately instead of checking if the exact term combinations are present. In my opinion, this is a best approach which copes with some cases where the order of the keyword terms wouldn&#8217;t alter their meanings or when articles or prepositions are used in the natural language while when looking up on the Internet they are neglected.</p>



<p>However, in case you would like to search for the exact matches, you could also use that approach by tweaking a bit this piece of code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import cloudscraper
from bs4 import BeautifulSoup

scraper = cloudscraper.create_scraper() 

for key, values in dict_urls.items():
    
    print(str(key))
    
    html = scraper.get(key, headers = {&quot;User-agent&quot; : &quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36&quot;})
    soup = BeautifulSoup(html.text)
    
    metatitle = (soup.find('title')).get_text()
    metadescription = soup.find('meta',attrs={'name':'description'})&#91;&quot;content&quot;]
    h1 = &#91;a.get_text() for a in soup.find_all('h1')]
    h2 = &#91;a.get_text() for a in soup.find_all('h2')]
    paragraph = &#91;a.get_text() for a in soup.find_all('p')]
    
    
    for y in values:
        
        metatitle_occurrence = &quot;True&quot;
        metadescription_occurrence = &quot;True&quot;
        h1_occurrence = &quot;True&quot;
        h2_occurrence = &quot;True&quot;
        paragraph_occurrence = &quot;True&quot;
        
        for z in y&#91;0].split(&quot; &quot;):
        
            if z not in str(metatitle).lower():
                metatitle_occurrence = &quot;False&quot;

            if z not in str(metadescription).lower():
                metadescription_occurrence = &quot;False&quot;

            if z not in str(h1).lower():
                h1_occurrence = &quot;False&quot;

            if z not in str(h2).lower():
                h2_occurrence = &quot;False&quot;

            if z not in str(paragraph).lower():
                paragraph_occurrence = &quot;False&quot;
            
        y.extend(&#91;metatitle_occurrence,metadescription_occurrence,h1_occurrence,h2_occurrence,paragraph_occurrence])
</pre></div>


<p>This piece of code will append to the dictionary a boolean value for each keyword and tag, throwing False is the keyword is not found and True if it is present. </p>



<h2 id="3-downloading-as-an-excel-file">3.- Downloading as an Excel file</h2>



<p>Finally, we will download this dictionary as an Excel file by using the library <a href="https://openpyxl.readthedocs.io/en/stable/">openpyxl</a>. This library will enable us to add the conditional formatting shown on the initial screenshot.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from openpyxl import Workbook
from openpyxl.formatting import Rule
from openpyxl.styles import Font, PatternFill, Border
from openpyxl.styles.differential import DifferentialStyle

wb=Workbook()
dest_filename = 'new_document.xlsx'
ws1 = wb.active

number=2

for key, values in dict_urls.items():
    
    ws1.cell(row=1,column=1).value= &quot;URL&quot;
    ws1.cell(row=1,column=2).value= &quot;Keyword&quot;
    ws1.cell(row=1,column=3).value= &quot;Ranking&quot;
    ws1.cell(row=1,column=4).value= &quot;Searches&quot;
    ws1.cell(row=1,column=5).value= &quot;Metatitle Occurrence&quot;
    ws1.cell(row=1,column=6).value= &quot;Metadescription Occurrence&quot;
    ws1.cell(row=1,column=7).value= &quot;H1 Occurrence&quot;
    ws1.cell(row=1,column=8).value= &quot;H2 Occurrence&quot;
    ws1.cell(row=1,column=9).value= &quot;Paragraph Occurrence&quot;
    
    for list_values in values:
        ws1.cell(row=number,column=1).value= key
        column = 2
        for iteration in list_values:
            ws1.cell(row=number, column=column).value = iteration
            column +=1
        number += 1
    

red_text = Font(color=&quot;9C0006&quot;)
red_fill = PatternFill(bgColor=&quot;FFC7CE&quot;)
green_text = Font(color=&quot;FFFFFF&quot;)
green_fill = PatternFill(bgColor=&quot;009c48&quot;)

dxf = DifferentialStyle(font=red_text, fill=red_fill)
dxf2 = DifferentialStyle(font=green_text, fill=green_fill)

rule = Rule(type=&quot;containsText&quot;, operator=&quot;containsText&quot;, formula=&#91;'A1:N' + str(number) + '= &quot;False&quot;'], dxf=dxf)
rule2 = Rule(type=&quot;containsText&quot;, operator=&quot;containsText&quot;, formula=&#91;'A1:N' + str(number) + '= &quot;True&quot;'], dxf=dxf2)

ws1.conditional_formatting.add('A1:N' + str(number), rule)
ws1.conditional_formatting.add('A1:N' + str(number), rule2)


wb.save(filename = dest_filename)
</pre></div>


<p>That is it, this will make the magic happen and export the data as an Excel file with the conditional formatting!</p>



<p></p>



<h2 id="4-getting-the-data-from-google-search-console">4.- Getting the data from Google Search Console</h2>



<p>Alternatively, you can also use the data from Google Search Console to run this piece of code. You would only need to extract the data by using GSC API or you can also just download the data with an Excel file and import it to your notebook with pandas as done before with the export from Semrush.</p>



<p>If you would like to give a try to GSC API and you are not familiar with it, I highly recommend you to have a look at the <a href="https://www.jcchouinard.com/google-search-console-api/">amazing guide that JC Chouinard </a>created to walk you through almost every single step of the set-up process.</p>



<p>When retrieving the data from Google Search Console API, it is possible that you will need to make some tweaks as the data is fetched day by day. In my case what I did is grouping the number of impressions and the average position by keyword and URL, simulating the export from Semrush.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
sum_df = df.groupby(&#91;'query','page']).agg({'impressions': 'sum', 'avg_position': 'mean'})
sum_df.avg_position = sum_df.avg_position.round(0)
sum_df = sum_df.sort_values(by=&#91;'impressions'], ascending=False)
</pre></div>


<p>After grouping the impressions and the average position, you can use the rest of the piece of code for the Semrush export with the data from Google Search Console.</p>



<figure class="wp-block-image size-full"><img width="1015" height="379" src="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.20.56.png" alt="" class="wp-image-1193" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.20.56.png 1015w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.20.56-300x112.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/08/Captura-de-pantalla-2021-08-10-a-las-20.20.56-768x287.png 768w" sizes="(max-width: 1015px) 100vw, 1015px" /></figure>



<p></p>



<p>That is all folks, I hope that you found this blog post interesting! </p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/onpage-optimization-python-seo/">On-page optimization with Python for SEO</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Guide to SEO on-page scraping with Python</title>
		<link>https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=guide-seo-onpage-scraping-python</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Tue, 29 Jun 2021 10:40:41 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1175</guid>

					<description><![CDATA[<p>Python can be a very useful resource to scrape and extract on-page data from a page. On this post I am going to share with you the most common Python keys to extract the most important information from a page from an SEO perspective. 1.- Making the web request First, before starting to parse the HTML code from a page in order to obtain the data which is of our interest, we need to make the request to the URL that we would like to scrape. The library that I usually use for these type of requests is cloudscraper, which&#8230; <span><a href="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/">Guide to SEO on-page scraping with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Python can be a very useful resource to scrape and extract on-page data from a page. On this post I am going to share with you the most common Python keys to extract the most important information from a page from an SEO perspective.</p>



<h2>1.- Making the web request</h2>



<p>First, before starting to parse the HTML code from a page in order to obtain the data which is of our interest, we need to make the request to the URL that we would like to scrape. The library that I usually use for these type of requests is <a href="https://pypi.org/project/cloudscraper/">cloudscraper</a>, which works in a very similar way to Requests but it is much better at accessing websites which use Cloudflare without being banned. If you are interested in web scraping with Python, you can also have a read at this article where I explain <a href="https://www.danielherediamejias.com/6-basic-tips-to-perform-web-scraping-with-python/">6 tricks for basic web scraping with Python.</a></p>



<p>Once we access the URL with cloudscraper, we will use <a href="https://pypi.org/project/beautifulsoup4/">BeautifulSoup</a> to parse the HTML code and obtain the SEO data.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import cloudscraper
from bs4 import BeautifulSoup

scraper = cloudscraper.create_scraper() 

html = scraper.get(&quot;&lt;your_url&gt;&quot;)
soup = BeautifulSoup(html.text)
</pre></div>


<h2>2.- Metas scraping</h2>



<h3>2.1.- Metatitle</h3>



<p>The metatitle is one of the main metas as we all the SEOs are already aware of as it is displayed on the search snippet and it can be optimized for determined keywords. The metatitle can be obtained with this key:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
metatitle = (soup.find('title')).get_text()
</pre></div>


<h3>2.2.- Metadescription</h3>



<p>Metadescriptions can be used to briefly explain what your page is about and they will also appear on the search snippet. The metadescription can be obtained with this key:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
metadescription = soup.find('meta',attrs={'name':'description'})&#91;&quot;content&quot;]
</pre></div>


<h3>2.3.- Robots</h3>



<p>The meta robots is a very important SEO tag as it specifies if the page can be indexed and it will give some directives about how the page is to be shown on the SERPs. Noindex, index, follow, nofollow, noarchive, nosnippet, notranslate, noimageindex or unavailable_after directives can be found here among others. If you are not familiar with some of these robots directives, you can find them all with their explanations <a href="https://developers.google.com/search/docs/advanced/robots/robots_meta_tag">over here</a>.</p>



<p>The key that we would need to use to extract this data is as follows and it will return a list with all the content directives which are separated by commas:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
robots_directives = soup.find('meta',attrs={'name':'robots'})&#91;&quot;content&quot;].split(&quot;,&quot;)
</pre></div>


<h3>2.4.- Viewport</h3>



<p>The meta viewport will indicate the visible part of the page and it is mainly important for mobile friendliness purposes. The meta viewport can be extracted with this key:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
viewport = soup.find('meta',attrs={'name':'viewport'})&#91;&quot;content&quot;]
</pre></div>


<h3>2.6.- Charset</h3>



<p>The meta charset will indicate to the search engine bots the character encoding. It is specially useful for those pages which are not written in English and might contain special characters not available in English when working on international SEO.</p>



<p>The meta charset can be found with this key: </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
charset = soup.find('meta',attrs={'charset':True})&#91;&quot;charset&quot;]
</pre></div>


<h3>2.7.- HTML language</h3>



<p>The html language is not a meta itself, but it can be an interesting element in case of working on international SEO as it gives to search engine bots some hints about what country the page is intended to target. </p>



<p>We can get this element with they key:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
html_language = soup.find('html')&#91;&quot;lang&quot;]
</pre></div>


<h2>3.- Alternates and canonicals scraping</h2>



<h3>3.1.- Canonical</h3>



<p>Rel=&#8221;canonical&#8221; indicates to Google what is the page that should be indexed. It can point to itself in case the own page is to be indexed or to another page if another page is meant to be indexed instead. It is important to mention that Google takes canonicals as recommendations, so it might be possible that it might disregard the canonical indication.</p>



<p>Canonical URLs can be obtained with this key: </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
canonical = soup.find('link',attrs={'rel':'canonical'})&#91;&quot;href&quot;]
</pre></div>


<h3>3.2.- Hreflangs</h3>



<p>Hreflangs are specially important when having a website with different language versions in order to indicate to search engines what version should be indexed and showcased in each country&#8217;s SERPs.</p>



<p>The key that needs to be used to extract hreflangs is as follows and it will return a list with the provided pages for each language and their country codes:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
list_hreflangs = &#91;&#91;a&#91;'href'], a&#91;&quot;hreflang&quot;]] for a in soup.find_all('link', href=True, hreflang=True)]
</pre></div>


<h3>3.3.- Mobile alternates</h3>



<p>They are used when a page has a mobile version which is hosted in a different page. This is the mainly the case of some non-responsive websites which do not use dynamic serving and have a mobile subdomain. </p>



<p>The mobile alternates can be extracted with: </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
mobile_alternate = soup.find('link',attrs={'media':'only screen and (max-width: 640px)'})&#91;&quot;href&quot;]
</pre></div>


<h2>4.- Schema mark-up scraping</h2>



<h3>4.1.- Quick schema mark-up overview</h3>



<p>It is especially easy to extract and analyze schema mark-up injections if they have been inserted in a JSON format. We can extract the whole script and then we can analyze it as if it were a Python dictionary. First, we need to find and parse the script with Beautifulsoup.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import json

json_schema = soup.find('script',attrs={'type':'application/ld+json'})
json_file = json.loads(json_schema.get_text())
</pre></div>


<p>Then after this, we can iterate over the schema mark-up and see at first glance which type of mark-ups are being used by that page:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
for x in json_file&#91;&quot;@graph&quot;]:
    print(x&#91;&quot;@type&quot;])
</pre></div>


<p>For instance:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img width="444" height="237" src="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-29-a-las-12.45.44.png" alt="" class="wp-image-1184" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-29-a-las-12.45.44.png 444w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-29-a-las-12.45.44-300x160.png 300w" sizes="(max-width: 444px) 100vw, 444px" /></figure></div>



<h3>4.2.- Breadcrumbs</h3>



<p>If the page has a breadcrumb list schema mark-up, we can use Python to extract the provided URLs in the mark-up and have an idea about which are its parental pages and its internal structure depth:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
breadcrumb_urls = &#91;&#91;x&#91;&quot;position&quot;],x&#91;&quot;item&quot;]] if &quot;item&quot; in str(x) else &#91;x&#91;&quot;position&quot;],&quot;Final URL&quot;] for x in json_file&#91;&quot;@graph&quot;]&#91;3]&#91;&quot;itemListElement&quot;]]
breadcumb_depth = len(breadcrumb_urls)
</pre></div>


<h2>5.- Content scraping</h2>



<h3>5.1.- Text</h3>



<h4>5.1.1.- Paragraphs</h4>



<p>The key which is as follows will scrape the paragraph texts (&#8220;p&#8221;) and will return a list with all the paragraph texts. In addition, we can calculate the number of characters from these texts.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
paragraph = &#91;a.get_text() for a in soup.find_all('p')]
#Text length
text_length = sum(&#91;len(a) for a in paragraph])
</pre></div>


<h4>5.1.2.- Headings</h4>



<p>Beautifulsoup enables us to extract a specific type of headers, let&#8217;s say for instance H1, but it also enables us to extract different type of tags by using the method find_all and introducing a list with all the tags that we would like to extract. So translating this into the code, it would be:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
h1 = &#91;a.get_text() for a in soup.find_all('h1')]
headers = soup.find_all(&#91;&quot;h1&quot;,&quot;h2&quot;,&quot;h3&quot;,&quot;h4&quot;,&quot;h5&quot;,&quot;h6&quot;])

#Cleaning the headers list to get the tag and the text as different elements in a list
list_headers = &#91;&#91;str(x)&#91;1:3], x.get_text()] for x in headers]
</pre></div>


<p>With Jupyter Notebook we can print HTML code and we can actually see how the headings hierarchy of that page looks like at first sight as the size of the font will be smaller or larger depending on the heading types:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from IPython.core.display import display, HTML
for x in headers:
    display(HTML(str(x)))
</pre></div>


<p>For example, the headers structure of my article about <a href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">scraping the SERPs with Oxylabs API</a> is represented as:</p>



<figure class="wp-block-image size-large"><img width="1024" height="497" src="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.38.41-1024x497.png" alt="" class="wp-image-1178" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.38.41-1024x497.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.38.41-300x145.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.38.41-768x372.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.38.41.png 1091w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3>5.2.- Images</h3>



<p>Something that might be interesting is scraping the image URLs from a page to have an idea about how many images are being used and their alt texts to see if they are properly optimized.</p>



<p>With this piece of code we can get all the image URLs from that page and their alt texts in a list format:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
images = &#91;&#91;a&#91;&quot;src&quot;],a&#91;&quot;alt&quot;]] if &quot;alt&quot; in str(a) else &#91;a&#91;&quot;src&quot;],&quot;&quot;] for a in soup.find_all('img')]
</pre></div>


<h3>5.3.- Links</h3>



<p>We can also extract the links and obtain two lists that will contain the internal and external links with their anchor texts and whether they are follow or nofollow.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
internal_links = &#91;&#91;a.get_text(), a&#91;&quot;href&quot;], &quot;nofollow&quot;] if &quot;nofollow&quot; in str(a) else &#91;a.get_text(), a&#91;&quot;href&quot;], &quot;follow&quot;] for a in soup.find_all('a', href=True) if &quot;&lt;your_domain&gt;&quot; in a&#91;&quot;href&quot;] or a&#91;&quot;href&quot;].startswith(&quot;/&quot;)]
external_links = &#91;&#91;a.get_text(), a&#91;&quot;href&quot;], &quot;nofollow&quot;] if &quot;nofollow&quot; in str(a) else &#91;a.get_text(), a&#91;&quot;href&quot;], &quot;follow&quot;] for a in soup.find_all('a', href=True) if &quot;&lt;your_domain&gt;&quot; not in a&#91;&quot;href&quot;] and not a&#91;&quot;href&quot;].startswith(&quot;/&quot;)]

#To get the number of links
number_internal_links = len(internal_links)
number_external_links = len(external_links)
</pre></div>


<p>On the other hand, we can also differentiate our links depending on where they are nested. So for instance, if the links are nested under a paragraph or a heading tag, we can assume that they are contextual links whereas if they are nested under div or span tags, we can assume that they will not be so.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
contextual_links = &#91;&#91;a.get_text(), a&#91;&quot;href&quot;], &quot;nofollow&quot;] if &quot;nofollow&quot; in str(a) else &#91;a.get_text(), a&#91;&quot;href&quot;], &quot;follow&quot;] for x in soup.find_all(&#91;&quot;p&quot;, &quot;h1&quot;, &quot;h2&quot;, &quot;h3&quot;, &quot;h4&quot;, &quot;h5&quot;, &quot;h6&quot;]) for a in x.find_all('a', href=True)]
div_links = &#91;&#91;a.get_text(), a&#91;&quot;href&quot;], &quot;nofollow&quot;] if &quot;nofollow&quot; in str(a) else &#91;a.get_text(), a&#91;&quot;href&quot;], &quot;follow&quot;] for x in soup.find_all(&#91;&quot;div&quot;,&quot;span&quot;]) for a in x.find_all('a', href=True)]
</pre></div>


<h2>6.- Open Graph scraping</h2>



<p>Last but not least, we can scrape the open graph and create a list that will contain the type of open graph and their specifications:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
open_graph = &#91;&#91;a&#91;&quot;property&quot;].replace(&quot;og:&quot;,&quot;&quot;),a&#91;&quot;content&quot;]] for a in soup.select(&quot;meta&#91;property^=og]&quot;)]
</pre></div>


<p>For example:</p>



<figure class="wp-block-image size-large"><img width="1024" height="256" src="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.56.46-1024x256.png" alt="" class="wp-image-1179" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.56.46-1024x256.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.56.46-300x75.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.56.46-768x192.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/Captura-de-pantalla-2021-06-28-a-las-0.56.46.png 1091w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>That is all folks, I hope that you find this guide helpful and if you happen to think of an element that I might have neglected, just let me know and I will add it to the guide! </p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/guide-seo-onpage-scraping-python/">Guide to SEO on-page scraping with Python</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Scraping the Google SERPs with Python and Oxylabs&#8217; API</title>
		<link>https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=scraping-google-serps-python-oxylabs</link>
		
		<dc:creator><![CDATA[danielheredia]]></dc:creator>
		<pubDate>Mon, 14 Jun 2021 18:29:38 +0000</pubDate>
				<category><![CDATA[Python Scripts for SEOs]]></category>
		<guid isPermaLink="false">https://www.danielherediamejias.com/?p=1155</guid>

					<description><![CDATA[<p>On today&#8217;s post I am going to show you how you can scrape the SERPs with Python and Oxylabs, which has a Real Time Crawler API that uses a global proxy pool that will prevent Google from banning your IP. In addition, I will also show you some cases where scraping the SERPs can be useful for several purposes such as indexation analyses, getting the number of indexed results, finding partners and/or sales opportunities, etcetera. Does it sound interesting? Let&#8217;s get started then! 1.- How does Oxylabs work? The Real Time Crawler service works in a very easy way, you&#8230; <span><a href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">( Read More )</a></span></p>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">Scraping the Google SERPs with Python and Oxylabs&#8217; API</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>On today&#8217;s post I am going to show you how you can scrape the SERPs with Python and Oxylabs, which has a <a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=284&amp;url_id=9">Real Time Crawler API</a> that uses a global proxy pool that will prevent Google from banning your IP. In addition, I will also show you some cases where scraping the SERPs can be useful for several purposes such as indexation analyses, getting the number of indexed results, finding partners and/or sales opportunities, etcetera. </p>



<p>Does it sound interesting? Let&#8217;s get started then! </p>



<figure class="wp-block-image size-large"><img width="1024" height="493" src="https://www.danielherediamejias.com/wp-content/uploads/2021/06/image-1024x493.png" alt="" class="wp-image-1168" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/06/image-1024x493.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/image-300x144.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/image-768x370.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/06/image.png 1251w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2>1.- How does Oxylabs work?</h2>



<p>The Real Time Crawler service works in a very easy way, you will only need to make a simple post HTTP request with the library <a href="https://docs.python-requests.org/en/master/">Requests</a> to their endpoint and then you will be able to retrieve the data from the SERPs. </p>



<p>For example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'adidas',
}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

print(response.json())
</pre></div>


<p>With this query we would scrape the SERPs for the query adidas from google.com. </p>



<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>It is worth mentioning that the requests support several parameters which can help us to customize our SERPs scraping. The parameters that I usually work with to customize the SERPs scraping are:</p>



<ul><li><strong>domain</strong>: you can introduce any specific Google cctld to extract the results from different country versions. </li><li><strong>query</strong>: this enables us to introduce our query. It also accepts Google&#8217;s commands such as &#8220;site:&#8221;, &#8220;intitle:&#8221; and so on as we will see in the practical examples. </li><li><strong>start_page</strong>: the page from which we would like to start the SERPs scraping.</li><li><strong>pages</strong>: number of pages to be scraped.</li><li><strong>limit</strong>: how many results will be displayed in each page. </li><li><strong>geo_location</strong>: the geographical location where the results will be adapted for. You can find and download as a CSV file all the geolocations from the <a href="https://developers.google.com/adwords/api/docs/appendix/geotargeting">Google Adwords geo-targets documentation</a>. </li><li><strong>user_agent_type</strong>: you can insert the type of device. The main values that I usually use in this parameter are either &#8220;desktop&#8221; or &#8220;mobile&#8221;, although you can also specify a browser as shown in<a href="https://docs.oxylabs.io/resources/user_agent_type.json"> this page which compiles are the supported user agents</a>. </li><li><strong>render</strong>: you can render the SERPs, which is a feature that is specially useful to get some of the features that are only accessible once the SERPs are rendered such as the carrousel of Google News and other rich snippets. The values that you can insert for this parameter are &#8220;html&#8221; or &#8220;png&#8221; (in case you would like to get a base64-encoded screenshot about how the rendered SERPs look like).</li><li><strong>parse</strong>: if this value is &#8220;true&#8221; the response will be structured in a JSON format. If not, it will be returned in a HTML format. </li></ul>



<p></p>



<p>It is important to clarify that if we need to render the results, we will need to use a different endpoint (https://data.oxylabs.io/v1/queries) and we will receive an URL in the response from which we will be able to access the results by using a Get request to the URL obtained from the key: [&#8220;_links&#8221;][1][&#8220;href&#8221;].</p>



<p>Here we can find an example about how our code would need to be in order to retrieve the results from a rendered SERPs page:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'adidas',
    'render':'html',
    'parse':'true'
}

# Get response.
response = requests.request(
    'POST',
    'https://data.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

response2 = requests.request(
                'GET',
                response.json()&#91;&quot;_links&quot;]&#91;1]&#91;&quot;href&quot;],
                auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
                json=payload,
            )

response_rendering = response2.json()&#91;&quot;results&quot;]
</pre></div>


<p>From my point of view, rendering the SERPs is the best way to get the most out of this tool, although if rendering is not necessary, we can save some time if we only parse the raw HTML code as rendering takes a bit of time. In fact, to avoid breaking the code due to the page not being rendered yet once the request is made, it is recommendable to make use of a while loop and time to get the response once it is eventually ready:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests
import time

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'adidas',
    'render':'html',
    'parse':'true'
}

# Get response.
response = requests.request(
    'POST',
    'https://data.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

response_rendering = None
while response_rendering is None:
    try:
        response2 = requests.request(
            'GET',
            response.json()&#91;&quot;_links&quot;]&#91;1]&#91;&quot;href&quot;],
            auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
            json=payload,
        )

        response_rendering = response2.json()&#91;&quot;results&quot;]
    except Exception as e:
        print(&quot;trying again&quot;)
        time.sleep(10)
        pass

</pre></div>


<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2>2.- What can you get by default?</h2>



<p>If we render the SERPs and we ask the data to be delivered in the structured data format we can get by default:</p>



<ul><li><strong>Paid results</strong>: with their positions, URLs, descriptions and titles. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;paid&#8221;].</li><li><strong>Organic results</strong>: with their positions, URLs, descriptions and titles. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;organic&#8221;].</li><li><strong>Video results</strong>: with their positions, URLs, titles and authors. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;videos&#8221;].</li><li><strong>Top stories</strong>: with their positions, sources, URLs and headlines. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;top_stories&#8221;].</li><li><strong>Related searches</strong>. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;related_searches&#8221;].</li><li><strong>Related questions</strong>: with their answers, source URLs and source titles. Key: response_rendering[0][&#8220;content&#8221;][&#8220;results&#8221;][&#8220;related_questions&#8221;].</li></ul>



<h3>2.1.- Creating a list with the paid results: URLs, titles and descriptions</h3>



<p>We will use a for loop to iterate over the JSON file and transform the results into a list:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
paid = &#91;]
for x in response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;paid&quot;]:
    paid.append(&#91;x&#91;&quot;pos&quot;],x&#91;&quot;url&quot;],x&#91;&quot;title&quot;], x&#91;&quot;desc&quot;]])
</pre></div>


<h3>2.2.- Creating a list with the organic results: URL, titles and description</h3>



<p>Same logic is used to get the organic results:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
organic_results = &#91;]
for x in response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    organic_results.append(&#91;x&#91;&quot;pos&quot;], x&#91;&quot;url&quot;], x&#91;&quot;title&quot;], x&#91;&quot;desc&quot;]])
</pre></div>


<h3>2.3.- Creating a list with the video results: URLs, titles and authors</h3>



<p>In this case, some of the keys are slightly different as we will extract the authors and the overall positions.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
video_results = &#91;]
for x in response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;videos&quot;]:
       video_results.append(&#91;x&#91;&quot;pos_overall&quot;], x&#91;&quot;url&quot;], x&#91;&quot;title&quot;], x&#91;&quot;author&quot;]])
</pre></div>


<h3>2.4.- Creating a list with the top stories results: URLs, headlines and sources</h3>



<p>Some of the keys are also different as we extract the headlines and the overall positions.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
top_stories = &#91;]
for x in response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;top_stories&quot;]:
       top_stories.append(&#91;x&#91;&quot;pos_overall&quot;], x&#91;&quot;url&quot;], x&#91;&quot;headline&quot;], x&#91;&quot;source&quot;]])
</pre></div>


<h3>2.5.- Creating a list with the related searches</h3>



<p>We can also extract the related searches and create a list with them:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
related_searches = &#91;]
related_searches.append(response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;related_searches&quot;]&#91;&quot;related_searches&quot;])
</pre></div>


<h3>2.6.- Creating a list with the related questions: questions, answers and source URLs</h3>



<p>Finally, we can get the related questions, their answers and their source URLs.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
related_questions = &#91;]
for x in response_rendering&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;related_questions&quot;]:
    related_questions.append(&#91;x&#91;&quot;pos&quot;],x&#91;&quot;question&quot;],x&#91;&quot;answer&quot;], x&#91;&quot;source&quot;]&#91;&quot;url&quot;]])
</pre></div>


<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2>3.- Some practical cases</h2>



<p>SERPs scraping can be used for an endless number of tasks and activities. Some of the tasks that I perform frequently by scraping the SERPs are checking the number of indexed results, indexation analyses, finding new partners, influencers and/or sales opportunities and finding guest-posting opportunities.</p>



<h3>3.1.- Number of indexed results</h3>



<p><a href="https://www.jcchouinard.com/get-number-of-indexed-pages-on-multiple-sites-with-python/">As JC Chouinard explained in this article</a>, it might be interesting to scrape the SERPs to extract the number of pages that are indexed for a query. If we combine this method with some Google commands like &#8220;intitle&#8221; or &#8220;inurl&#8221;, we can have an idea about not only the number of indexed pages for a query, but the number of pages which contain the keyword that we would like to target in their metatitles. </p>



<p>Theoretically, if we include a keyword exact match in our metatitle we might have many more chances to rank for that specific keyword than those pages where the keyword is not mentioned in the title. Therefore, in order to evaluate how competitive a keyword is, it might make sense to make use of the command &#8220;intitle&#8221; and extract the number of indexed results.</p>



<p>This is something we can do with the <a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=284&amp;url_id=9">Real Time Crawler</a> without having to render the SERPs page and using <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup</a> to create an object that can be parsed as shown below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests
from bs4 import BeautifulSoup

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'intitle:&quot;buy white shoes&quot;',
}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_title&gt;'),
    json=payload,
)

soup = BeautifulSoup(response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;], &quot;html.parser&quot;)
div_results = soup.find(&quot;div&quot;, {&quot;id&quot;: &quot;result-stats&quot;})
indexed_results = int(div_results.text.split(&quot;About&quot;)&#91;1].split(&quot;results&quot;)&#91;0].replace(&quot; &quot;,&quot;&quot;).replace(&quot;,&quot;,&quot;&quot;))

</pre></div>


<p>With this technique, we can analyze the competitiveness of lots of keywords in a bulk mode to allocate our efforts on those not so competitive keywords whose ROI might be higher.</p>



<h3>3.2.- Indexation analyses</h3>



<p>Another task that can be done with proxies and the command &#8220;site:&#8221; is indexation analyses (only a few indexed URLs from a site are provided on Google Search Console unfortunately). To proceed with these analyses, we will not need to render the SERPs and we will basically use the parameter &#8220;parse&#8221; to get the data as a JSON file. If there are no indexed URLs, then the organic results will be blank. </p>



<p>For instance, with this piece of code I was able to retrieve all the results that are indexed from my site:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'site:danielherediamejias.com',
    'parse':'true',
    'start_page':1,
    'limit':100,
    'pages':3
    
}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

number_indexed_results = len(response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;])

indexed_urls = &#91;]
for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    indexed_urls.append(&#91;x&#91;&quot;url&quot;]])
</pre></div>


<p>We can also introduce a bunch of URLs from a sitemap for example and we can check whether they are indexed or not in order to understand what is the indexation coverage from that sitemap. For this, we will need to retrieve the results from the SERPs and check if they exactly match with the original URL since there could be some cases where the root URL might not be indexed but there could be some indexed subdirectories.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

url = &quot;&lt;your_url&gt;&quot; 

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'site:' + url,
    'parse':'true',
    'start_page':1,
    'limit':100,
    'pages':3
    
}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)


indexation = False
for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    try:
        if x&#91;&quot;url&quot;].endswith(url):
            indexation = True
            break
    except:
        pass
</pre></div>


<p>Regarding these indexation analyses, we can also introduce an initial domain, use the command site: and extract all the provided URLs by Google (around 300), store them and iterate again over all the URLs that have been extracted. This is specially useful in very large websites and it will give you an idea about all the URLs that are indexed from a site and you might be able to discover pages and sections that were not supposed to be indexed. </p>



<p>Basically, what we will do is creating a list with all the URLs and append more URLs as we iterate over the URLs and get more results from Google&#8217;s index:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

list_url = &#91;&quot;&lt;your_initial_url&gt;&quot;]

for iteration in list_url:
    
    print(iteration)
    payload = {
        'source': 'google_search',
        'domain': 'com',
        'query': 'site:' + iteration,
        'parse':'true',
        'start_page':1,
        'limit':100,
        'pages':3

    }

    response = requests.request(
        'POST',
        'https://realtime.oxylabs.io/v1/queries',
        auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
        json=payload,
    )


    for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
        present = False
        try:
            for y in list_url:
                if x&#91;&quot;url&quot;] == y:
                    present = True
            
            if present == False:
                list_url.append(x&#91;&quot;url&quot;])
        except:
            pass
</pre></div>


<p>This process is quite proxy-consuming because essentially we will use the command site for all the pages that are found with the proxies and in some cases it can be inaccurate if the number of pages which are indexed from a directory exceed the 300 results (which is usually the maximum number of results that Google will return for a query). </p>



<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h3>3.3.- Finding new partners, influencers and/or sales opportunities</h3>



<p>The proxies can be used not only for SEO purposes, but also for finding email addresses to collaborate with new partners and/or influencers or to find new sales opportunities. </p>



<p>How can we do this? We can use some commands like site and intext to search for indexed results from a website with email addresses. For instance, if we would like to find Youtube channels with email addresses that might be open to collaborations we could use the search pattern: &#8220;site:youtube.com inurl:/about/ intext:@gmail.com&#8221;.</p>



<p>First we use the proxies and we extract the URLs:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'site:youtube.com inurl:/about/ intext:@gmail.com',
    'parse':'true',
    'limit':100

}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

list_url = &#91;]
for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    present = False
    try:
        list_url.append(&#91;x&#91;&quot;url&quot;]])
    except:
        pass
</pre></div>


<p>Then, once we extract the indexed URLs, we can go over them with requests and beautifulsoup and extract the email addresses to build our own database and contact them.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
import time
import re

driver = webdriver.Chrome(ChromeDriverManager().install())

for iteration in range (len(list_url)):
    
    
    driver.get(list_url&#91;iteration]&#91;0])
    
    if iteration == 0:
        input = driver.find_element_by_xpath('/html/body/c-wiz/div/div/div/div&#91;2]/div&#91;1]/div&#91;4]/form/div&#91;1]/div/button/span')
        input.click()

    html = driver.page_source
    email_address = re.findall(r&quot;&#91;a-z0-9\.\-+_]+@&#91;a-z0-9\.\-+_]+\.&#91;a-z]+&quot;, html)
    email_address  = list(dict.fromkeys(email_address))
    list_url&#91;iteration].append(email_address)
    
    time.sleep(2)
    
driver.close()
</pre></div>


<h3>3.4.- Finding Guest-posting opportunities</h3>



<p>Another activity that can be automated with proxies is the the discovery of new guest-posting opportunities. This task is pretty straightforward, you will only need to extract the URLs returned from queries such as “write with us”, “write for us”, “guest posting policy”, “guest posting rules”, “guest posting guidelines”, &#8220;we accept guest post”, “submit a guest post&#8221;&#8230;</p>



<p>If you would like to narrow down the results, you can also add a representative term about the topic of the websites that you are looking for:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': '&quot;write with us&quot; digital marketing',
    'parse':'true',
    'limit':100

}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

list_url = &#91;]
for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    present = False
    try:
        list_url.append(&#91;x&#91;&quot;url&quot;]])
    except:
        pass
</pre></div>


<p>As an extra step, something that can be done is checking if those websites have Doubleclick ads. In such a case, it is very possible that they are monetizing their website and they might be open to guest-posting in their sites.</p>



<p>You can search for Google Doubleclick ads with:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
import time

driver = webdriver.Chrome(ChromeDriverManager().install())

for iteration in range (len(list_url)):
       
    driver.get(list_url&#91;iteration]&#91;0])
    html = driver.page_source
    
    guest_posting = False
    if &quot;doubleclick&quot; in html:
        guest_posting = True
    
    list_url&#91;iteration].append(guest_posting)
    
    time.sleep(2)
    
driver.close()
</pre></div>


<p>In addition, if you would like to categorize the extracted websites based on their topics in a bulk mode, you can have a read at this post where I explain <a href="https://www.danielherediamejias.com/website-categorization-python/">how you can use Python and Google NLP API for website categorization</a>. </p>



<h3>3.5.- Recruitment</h3>



<p>Last but not least, proxies can also be used for recruitment and finding suitable candidates for some open positions. If for instance, we would be searching for a candidate to cover an SEO position in Barcelona who needs to be able to code with Python, we could use the command: site:es.linkedin.com/in/ intitle:&#8221;seo&#8221; intext:barcelona intext:python.</p>



<p>Therefore, getting the profiles from Google&#8217;s index would be quite easy with:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import requests

payload = {
    'source': 'google_search',
    'domain': 'com',
    'query': 'site:es.linkedin.com/in/ intitle:&quot;seo&quot; intext:barcelona intext:python',
    'parse':'true',
    'limit':100

}

response = requests.request(
    'POST',
    'https://realtime.oxylabs.io/v1/queries',
    auth=('&lt;your_username&gt;', '&lt;your_password&gt;'),
    json=payload,
)

list_url = &#91;]
for x in response.json()&#91;&quot;results&quot;]&#91;0]&#91;&quot;content&quot;]&#91;&quot;results&quot;]&#91;&quot;organic&quot;]:
    present = False
    try:
        list_url.append(&#91;x&#91;&quot;url&quot;]])
    except:
        pass
</pre></div>


<figure class="wp-block-image size-large"><a href="https://oxylabs.io/products/scraper-api/serp?utm_source=Daniel+Heredia&amp;utm_medium=affiliate&amp;adgroupid=284&amp;transaction_id=10297089d262be58326fcda39ce5c0"><img width="1024" height="553" src="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png" alt="" class="wp-image-1298" srcset="https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1024x553.png 1024w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-300x162.png 300w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-768x415.png 768w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner-1536x830.png 1536w, https://www.danielherediamejias.com/wp-content/uploads/2021/12/Scraper-APIs-Free-trial-banner.png 2040w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2>4.- Alternatives to scrape the SERPs for free</h2>



<p>There are some alternatives to scrape the SERPs for free, although unfortunately they will not offer as many features as Oxylabs is able to offer.</p>



<h3>4.1.- Googlesearch Python library</h3>



<p><a href="https://github.com/MarioVilas/googlesearch">Mario Vilas created a library</a> that enables you to scrape the SERPs without using proxies. However, after around 20 requests, it is possible that Google will ban your IP and you will not be able to keep the scraping up.</p>



<p>This piece of code will return the URLs showing up for the query adidas with the Spanish browser:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
from googlesearch import search
for url in search('adidas', tld='es', lang='es', stop=20):
    print(url)
</pre></div>


<h3>4.2.- Google Custom Search API</h3>



<p>Koray Tuğberk explained on this article <a href="https://www.holisticseo.digital/python-seo/data-science/">how to Google Custom Search API</a> to retrieve the results turning up for a specific query via <a href="https://pypi.org/project/advertools/">advertools</a>. You will only need to create a project on Google Developer Console, get your credentials and enable the Google Custom Search API.</p>



<p>Once you have obtain your credentials, you can make use of this API by using this piece of code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import advertools as adv
api_key, cse_id = &quot;YOUR API KEY&quot;, &quot;YOUR CSE ID&quot;
adv.serp_goog(key=api_key, cx=cse_id, q=&quot;Example Query&quot;, gl=&#91;&quot;example country code&quot;])
</pre></div>


<p>Unfortunately this API does not support most of the SERPs rich snippets or commands, so if you would like to make a more extensive analysis, you might need to use premium tool like the <a href="https://oxylabs.go2cloud.org/aff_c?offer_id=7&amp;aff_id=284&amp;url_id=9">Real Time Crawler</a> from Oxylabs.</p>



<h3>4.3.- Chrome extensions</h3>



<p>If you would need to scrape the SERPs but you do not really need to to it for many queries, you can also make use of Chrome extensions to extract the indexed results. You can use Web Scraper <a href="https://www.danielherediamejias.com/scrape-the-serps-with-a-chrome-extension/">as I explained in this article to scrape the SERPs with a Google Chrome extension</a>.</p>



<h2>5.- FAQ section</h2>



<div class="schema-faq wp-block-yoast-faq-block"><div class="schema-faq-section" id="faq-question-1623694840997"><strong class="schema-faq-question">Can I use the Real Time Crawler from Oxylabs with Python?</strong> <p class="schema-faq-answer">Yes, the Real Time Crawler supports Python language, so you can use Python to scrape the Google SERPs as explained in this blog post.</p> </div> <div class="schema-faq-section" id="faq-question-1623694917252"><strong class="schema-faq-question">What can I do with proxies for SEO?</strong> <p class="schema-faq-answer">Rankings monitoring for organic, paid, top stories and video results, indexation analyses, getting the number of indexed pages for a query, finding guest-posting opportunities&#8230;</p> </div> <div class="schema-faq-section" id="faq-question-1623695051351"><strong class="schema-faq-question">How long will it take me to use Oxylabs with Python?</strong> <p class="schema-faq-answer">Not long, as you can use most of my code samples although you might need to make small changes.</p> </div> </div>
<p>La entrada <a rel="nofollow" href="https://www.danielherediamejias.com/scraping-google-serps-python-oxylabs/">Scraping the Google SERPs with Python and Oxylabs&#8217; API</a> se publicó primero en <a rel="nofollow" href="https://www.danielherediamejias.com">Daniel Heredia</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
