<?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>Antoine Eripret</title>
	<atom:link href="https://dev.aeripret.com/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description></description>
	<lastBuildDate>Tue, 28 Feb 2023 09:14:24 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>SQL for SEO</title>
		<link>https://dev.aeripret.com/sql-for-seo/</link>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Tue, 28 Feb 2023 09:14:23 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=716</guid>

					<description><![CDATA[<p>Some days ago, Google announced a major improvement for Google Search Console: a simple data export to BigQuery. To be honest, I haven&#8217;t tried it yet but no matter what, it is good news. This means several things: I&#8217;ve been using SQL for almost 3 years now, and the objective of this post is to [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/sql-for-seo/">SQL for SEO</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Some days ago, Google announced a major improvement for Google Search Console: a<a href="https://developers.google.com/search/blog/2023/02/bulk-data-export" target="_blank" rel="noreferrer noopener"> simple data export to BigQuery</a>. To be honest, I haven&#8217;t tried it yet but no matter what, it is good news. This means several things: </p>



<ul>
<li>Companies will be able to extract easily their GSC data to their data lakes. It was already possible previously if you knew how to extract the GSC data, through the API, to BigQuery, but this new integration will be easier and includes more data points. </li>



<li>I already explained in a previous post what <a href="https://www.dev.aeripret.com/gsc-data/">the limitations of the API </a>were, I hope this integration will solve at least a part of them </li>



<li><strong>SEO professionals will start using SQL more extensively.</strong> Some of them were already doing it, but this integration will increase the necessity of knowing at least the basics of this language. If you are worried about the cost: don&#8217;t! I have currently a database with 150M rows in BigQuery, <strong>and it costs me less than $5 per month to use it. </strong></li>
</ul>



<p>I&#8217;ve been using SQL for almost 3 years now, and the objective of this post is to explain the basics of SQL for SEO analysis and some simple, yet powerful, use cases. <strong><span style="text-decoration: underline;">This is not an ultimate guide</span></strong>, and if you have advanced need you can have a look at the following courses: <a href="https://www.codecademy.com/learn/learn-sql">Codecademy</a>, <a href="https://www.sololearn.com/learning/1060">Sololearn</a>, <a href="https://www.dataquest.io/path/sql-skills/" target="_blank" rel="noreferrer noopener">Dataquest</a>. </p>



<h2 class="wp-block-heading">Why should you learn SQL? </h2>



<p>SQL is the most common language used to query modern databases. This includes simple website databases (such as the WordPress I&#8217;m using), but also more advanced data warehouses such as BigQuery. You can use it without knowing SQL through <a href="https://workspace.google.com/blog/product-announcements/connected-sheets-is-generally-available" target="_blank" rel="noreferrer noopener">Connected Sheets</a>, but you&#8217;ll still need to write SQL in some cases. </p>



<figure class="wp-block-image size-large is-resized"><img decoding="async" src="https://storage.googleapis.com/gweb-cloudblog-publish/original_images/ConnectedSheetsSetup.gif" alt="" width="581" height="426"/></figure>



<p>Apart from that, bear in mind that Excel and Sheets have limitations and if you have to handle many rows, you may face some issues. Why? Because these tools were not designed to hold millions of rows, when databases are specifically designed for that. </p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" loading="lazy" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-16.png" alt="" class="wp-image-760" width="509" height="261" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-16.png 678w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-16-300x154.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-16-320x164.png 320w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-16-480x246.png 480w" sizes="(max-width: 509px) 100vw, 509px" /></figure>



<p>Other advantages of SQL that are worth mentioning: </p>



<ul>
<li>Easier learning curve than most programming languages such as Python. You can get started in a couple of hours. </li>



<li>Unlike Excel, your logic will be easier to understand because it will be fully written. <strong>Forget the pain to reverse engineer the logic behind a full Excel file. </strong></li>



<li>You can run the same query several times (same task, different projects &#8230;) and save time to focus on tasks where you have more added value. </li>
</ul>



<h2 class="wp-block-heading">Do you need to learn SQL as an SEO? </h2>



<p>No! SQL is not mandatory, but <strong>depending on the projects you work on, it can help you work better and/or faster</strong>. This should not be at the top of your hard skills list if you&#8217;re starting your training in this field. </p>



<h2 class="wp-block-heading">SQL basics </h2>



<p>To explain the SQL basics, I&#8217;ll be working with a GSC database including two dimensions (page &amp; date) and four metrics (clicks, impressions, ctr, position). If you&#8217;ve opened a GSC dashboard before, you should be familiar with all of these. If that&#8217;s not the case, they are defined in <a href="https://support.google.com/webmasters/answer/7576553?hl=en" target="_blank" rel="noreferrer noopener">the documentation.</a> </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="261" height="225" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-15.png" alt="" class="wp-image-759"/></figure>



<p>A simple SQL query is usually composed of three parts: </p>



<ul>
<li><strong>SELECT: </strong>the columns we want to get. You&#8217;re often interested in a limited number of columns, even if your table has a lot of them. </li>



<li><strong>FROM</strong>: the table we extract the data from. You&#8217;ll usually have a dataset composed of several tables. You need to specify the name; otherwise SQL won&#8217;t know which table you want to extract the data from. </li>



<li><strong>WHERE: </strong>the logic we apply to our data. In our example below, we just want the total of clicks after the 01/01/2022. </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="286" height="209" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-18.png" alt="" class="wp-image-762"/></figure>



<p>Simple, right? <strong>SELECT, FROM and WHERE are called SQL clauses. </strong>They have specific meanings (see <a href="https://data-flair.training/blogs/sql-clauses/" target="_blank" rel="noreferrer noopener">full list)</a> and must be used in a specific order. You cannot use the <strong>FROM </strong>before the <strong>SELECT </strong>for instance: SQL will throw an error. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="309" height="292" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-19.png" alt="" class="wp-image-763" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-19.png 309w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-19-300x283.png 300w" sizes="(max-width: 309px) 100vw, 309px" /></figure>



<p>As I mentioned, the objective of this article is not to explain everything to master SQL, but the basics you need to leverage your data. In the following sections, I&#8217;ll explain what the most important clauses are. </p>



<h3 class="wp-block-heading">SELECT</h3>



<p>The SELECT clause is a fundamental component of SQL that is used to retrieve data from one or more tables in a database. Some tips: </p>



<ul>
<li>You can specify a new name for your columns using the <strong>AS </strong>statement followed by the new name. I highly recommend doing that when you use aggregation methods (such as <strong>SUM()</strong> in my example) in BigQuery because otherwise you&#8217;ll end up with names such as <strong>f0_ </strong>which are confusing. </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="245" height="192" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-21.png" alt="" class="wp-image-765"/></figure>



<ul>
<li>If you want to select more than one column, you must separate the names by a comma. If for any reason you need to select all columns, use <strong>select *</strong>. Do not do that on huge tables because <strong>it may take a while and/or cost you a lot if you&#8217;re using BigQuery</strong>. </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="268" height="198" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-22.png" alt="" class="wp-image-766"/></figure>



<h3 class="wp-block-heading">WHERE</h3>



<p>As we already explained, the <strong>WHERE </strong>clause is used to apply conditions. You can apply conditions on columns that are not inside your <strong>SELECT </strong>clause, which is one of the advantages of SQL over Excel. It is actually what I did in my previous examples. Some comments on this clause: </p>



<ul>
<li>You can obviously combine conditions using the <strong>AND </strong>or <strong>OR </strong>instructions</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="243" height="194" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-23.png" alt="" class="wp-image-767"/></figure>



<ul>
<li>You can use the most common mathematical operators, such as <strong> = </strong>(equal to), <strong>!= </strong>(not equal to) etc&#8230; </li>



<li>To avoid creating countless conditions, you can use the <strong>IN </strong> to check if a column is equals to any of a list of provided values</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="392" height="203" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-24.png" alt="" class="wp-image-768" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-24.png 392w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-24-300x155.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-24-320x166.png 320w" sizes="(max-width: 392px) 100vw, 392px" /></figure>



<ul>
<li>To check if a column starts, ends or contains a specific string, you can use the <strong>LIKE </strong>operator. With this operator, the percent symbol must be interpreted as &#8220;anything or nothing&#8221;. 
<ul>
<li><strong>wik%</strong> will check if a string starts with &#8220;wik&#8221;</li>



<li><strong>%wik</strong> will check if a string ends with &#8220;wik&#8221;</li>



<li><strong>%wik% </strong>will check if a string contains &#8220;wik&#8221;</li>
</ul>
</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="307" height="210" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-25.png" alt="" class="wp-image-769" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-25.png 307w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-25-300x205.png 300w" sizes="(max-width: 307px) 100vw, 307px" /></figure>



<h2 class="wp-block-heading">GROUP BY </h2>



<p>This clause allows you to group data. As I explained, our table includes data per URL and per date: how can we get the total by date, for instance? Using <strong>GROUP BY! </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="248" height="350" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-26.png" alt="" class="wp-image-772" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-26.png 248w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-26-213x300.png 213w" sizes="(max-width: 248px) 100vw, 248px" /></figure>



<p>Some important details: </p>



<ul>
<li>This operation is similar to a pivot table in Excel. </li>



<li>You cannot group data by a column you didn&#8217;t add within your <strong>SELECT </strong>clause. This would throw an error. </li>



<li>If you use <strong>GROUP BY </strong>with <strong>ORDER BY </strong>and <strong>LIMIT</strong>, you can answer to questions such as &#8220;What are the X most performing pages&#8221;</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="456" height="363" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-27.png" alt="" class="wp-image-773" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-27.png 456w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-27-300x239.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-27-320x255.png 320w" sizes="(max-width: 456px) 100vw, 456px" /></figure>



<h2 class="wp-block-heading">CASE</h2>



<p>The <strong>CASE </strong>statement is useful when you want to create a new column in your result based on one or several conditions. For instance, let&#8217;s imagine that you would like to understand what percentage of the traffic is generated by the homepage and compare it to the rest. You can create a simple SQL <strong>CASE </strong>to do just that: </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="868" height="287" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-28.png" alt="" class="wp-image-774" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-28.png 868w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-28-300x99.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-28-768x254.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-28-320x106.png 320w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-28-480x159.png 480w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-28-800x265.png 800w" sizes="(max-width: 868px) 100vw, 868px" /></figure>



<ul>
<li>You can include as many conditions as you want, but you always need to follow the <strong>WHEN {conditions} THEN {value} </strong>logic </li>



<li>You need to end your conditions by an <strong>END </strong>and then assign a name to your new column </li>
</ul>



<p>In an SEO context, you will often use this clause to categorize your pages, even if there are other use cases, obviously. </p>



<h2 class="wp-block-heading">JOIN </h2>



<p>The main advantage you have when you add your data from several sources in a data warehouse, is the ability to easily merge them. You can, for instance, <strong>get the number of orphan pages but generating traffic on your site</strong> using two different data sources: </p>



<ul>
<li>The GSC table we already used in our previous examples </li>



<li>A new table, containing the data from a crawl done with <a href="https://screamingfrog.co.uk/" target="_blank" rel="noreferrer noopener">Screaming Frog</a> or <a href="https://www.oncrawl.com/" target="_blank" rel="noreferrer noopener">Oncrawl</a> </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="332" height="229" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-29.png" alt="" class="wp-image-775" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-29.png 332w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-29-300x207.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-29-320x221.png 320w" sizes="(max-width: 332px) 100vw, 332px" /></figure>



<p>In Excel, if you want to merge data, you&#8217;re very likely to use the <strong>VLOOKUP</strong> formula. Its closest equivalent in SQL is <strong>JOIN,</strong> and the most common are: </p>



<ul>
<li><strong>INNER JOIN: </strong>you merge data by combining rows that have matching values in the two tables </li>



<li><strong>LEFT JOIN: </strong>you merge data by keeping all data from the left table. When you use VLOOKUP in Excel, you usually perform an operation similar to this kind of <strong>JOIN</strong>. </li>



<li><strong>FULL OUTER JOIN: </strong>you merge data by keeping data from both tables </li>
</ul>



<p>If you&#8217;re not familiar with these concepts, the following image (<a href="https://stackoverflow.com/questions/38549/what-is-the-difference-between-inner-join-and-outer-join" target="_blank" rel="noreferrer noopener">source</a>) should help you understand: </p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" loading="lazy" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-31.png" alt="" class="wp-image-779" width="417" height="309" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-31.png 834w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-31-300x222.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-31-768x569.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-31-320x237.png 320w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-31-480x356.png 480w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-31-800x593.png 800w" sizes="(max-width: 417px) 100vw, 417px" /></figure>



<p>Going back to our <strong>gsc </strong>and <strong>screaming_frog </strong> tables, we can for instance use the <strong>JOIN </strong>statement to get the number of clicks by depth using the following logic: </p>



<ul>
<li>The clicks will come from the <strong>gsc </strong>table </li>



<li>The depth will come from the <strong>screaming_frog </strong>table </li>



<li>These two tables are merged using the <strong>page </strong>column </li>
</ul>



<p>The code we&#8217;d use will be the following: </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-sql" data-lang="SQL"><code>select 
--the data we need 
--the column name starts with the name of the table followed by a dot because we&#39;re using more than one 
--It is not mandatory if you don&#39;t have columns with the same names in your tables
SUM(gsc.clicks) as clicks,
screaming_frog.depth
--our left table 
from gsc
--our right table and the join logic 
left join screaming_frog on gsc.page = screaming_frog.URL
--rest of the SQL code
group by depth
order by depth asc
</code></pre></div>



<p>This code would return the following table. Note that we have a row with no depth information: these are the URLs found in the <strong>gsc</strong> table, but not in <strong>screaming frog. </strong>If we had performed an <strong>INNER </strong>or <strong>RIGHT </strong>join, this row would not be here. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="642" height="680" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-32.png" alt="" class="wp-image-784" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-32.png 642w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-32-283x300.png 283w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-32-320x339.png 320w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-32-480x508.png 480w" sizes="(max-width: 642px) 100vw, 642px" /></figure>



<h2 class="wp-block-heading">Conclusion</h2>



<p>Is that it? Of course not, but if you master these concepts, you&#8217;ll be able to start using SQL. Other concepts, such as subqueries, can be learned along the way when you&#8217;ve mastered the basics. </p>



<p>Again, SQL is not a mandatory hard skill to work in SEO, but it will definitely improve the way you work if you have to deal with a lot of data for your clients or projects. </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/sql-for-seo/">SQL for SEO</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to retrieve search volume at scale?</title>
		<link>https://dev.aeripret.com/retrieve-search-volume-at-scale/</link>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Thu, 09 Feb 2023 14:48:44 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=656</guid>

					<description><![CDATA[<p>For quite some time, the SEO industry has been arguing over what is commonly referred to as zero-volume keywords. You can read these two articles (one and two) and make up your mind on this topic. I actually have countless examples where writing on zero-volume topics yielded remarkable results, as the one shown below. There [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/retrieve-search-volume-at-scale/">How to retrieve search volume at scale?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For quite some time, the SEO industry has been arguing over what is commonly referred to as zero-volume keywords. You can read these two articles (<a href="https://www.searchenginejournal.com/why-you-should-target-zero-search-volume-keywords/458522/#:~:text=What%20Are%20Zero%20Search%20Volume,volumes%20and%20are%20extremely%20specific." target="_blank" rel="noreferrer noopener">one</a> and <a href="https://ahrefs.com/blog/zero-volume-keywords/" target="_blank" rel="noreferrer noopener">two</a>) and make up your mind on this topic. I actually have countless examples where writing on zero-volume topics yielded remarkable results, as the one shown below. There are usually no secret recipe: you need to know your audience and/or get lucky to encounter an easy SERP. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="269" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-7-1024x269.png" alt="" class="wp-image-673" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-7-1024x269.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-7-300x79.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-7-768x202.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-7-1536x404.png 1536w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-7-2048x539.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>That being said, there are situations where search volumes are useful and accurate. It&#8217;s easy to get the data for a couple of hundreds with any mainstream tool or the <a href="https://ads.google.com/home/tools/keyword-planner/" target="_blank" rel="noreferrer noopener">Google Keyword Planner</a> if you have running ads (otherwise you&#8217;ll get volume brackets). But how can you retrieve search volumes for thousands or even millions of keywords? In this article, I&#8217;ll go through my go-to solution. </p>



<h2 class="wp-block-heading">Why would you even need data for thousands of keywords? </h2>



<p>This is a legit question you may ask yourself. At the end of the day, we usually don&#8217;t need to get the search volume for every keyword we have in our Google Search Console, for instance. That&#8217;s true!</p>



<p>But imagine for a moment that you work for the French national railway company (SNCF). The French network is composed of more than 3,000 train stations (<a href="https://www.sncf.com/fr/groupe/profil-et-chiffres-cles/portrait-entreprise/qui-sommes-nous/sncf-gares-connexions" target="_blank" rel="noreferrer noopener">source</a>), which ends up creating a bit less than 9,000,000 possible routes between them. You&#8217;ll obviously want to cover the most searched in your architecture, and you&#8217;ll have to base your decision on: </p>



<ul>
<li><strong>First-part data</strong> such as internal searches and sales </li>



<li><strong>Third-part data, </strong>such as the number of searches on Google and other search engines </li>
</ul>



<p>And this is where the ability to retrieve search volume at scale will be handy! </p>



<h2 class="wp-block-heading">Usual approach</h2>



<p>If you want to get data for your keywords, you usually have two options. </p>



<p><strong><span style="text-decoration: underline;">Google Keyword Planner</span></strong></p>



<p>This tool from Google can be the easiest option in most cases. Nevertheless, you cannot add more than 700 keywords at once. If you have a couple of thousands, you could add them by batch, but it quickly becomes annoying and time-consuming. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="494" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-8-1024x494.png" alt="" class="wp-image-676" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-8-1024x494.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-8-300x145.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-8-768x370.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-8-1536x740.png 1536w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-8-2048x987.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>There is still the Google Ads API, but setting it up is complex and requires making your way through Google&#8217;s documentation. </p>



<p><strong><span style="text-decoration: underline;">Other tools </span></strong></p>



<p>Some tools have an API to get keyword data, including search volumes. But I don&#8217;t often recommend using most of them for many reasons: </p>



<ul>
<li><strong>Price: </strong>the API is (almost) always only available with the most expansive plans. Moreover, you often have to spend a lot of API credits to get all the information you need. </li>



<li><strong>Documentation: </strong>the documentation (even if Semrush has improved a lot on that) is bad. Have a look at what ScrapingBee does (<a href="https://www.scrapingbee.com/documentation/#" target="_blank" rel="noreferrer noopener">source</a>) where everything is well-explained, and code snippets can be copied. And then compare it to most SEO providers. I really don&#8217;t like spending an hour figuring out how the API works.  </li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="363" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-9-1024x363.png" alt="" class="wp-image-677" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-9-1024x363.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-9-300x106.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-9-768x273.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-9-1536x545.png 1536w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-9-2048x727.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul>
<li><strong>Batch requests: </strong>one critical features for our usage is to be able to retrieve data by batch, meaning that one call can return the data of hundreds or thousands of keywords. Most of the API I&#8217;ve tried don&#8217;t allow it. </li>
</ul>



<h2 class="wp-block-heading">How do I do it? </h2>



<p>Obviously, I&#8217;m not writing this article to let you down and tell you that you can&#8217;t retrieve keyword volume at scale. I&#8217;ve discovered an API (<a href="https://dataforseo.com/" target="_blank" rel="noreferrer noopener">DataForSEO</a>) that has all the required functionalities you need. <strong>DISCLAIMER: I&#8217;m not paid by the company to write this article. I just happen to be a delighted customer, but they are not well-known.</strong></p>



<p>The only issue: it&#8217;s an API; therefore you need to write code or use a no-code tool to do it for you. But you can get the volume for 1,000,000 keywords in less than an hour for just $50. Amazing, right? Also, you can get data for Google but also Bing. <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>I won&#8217;t provide the codes you need to make the calls because: </p>



<ul>
<li>I work in Python, but you may use another language. If you are also using Python, have a look at <a href="https://github.com/databulle/DataForSEO-SERP-API-Python-Client">this repository</a> to speed your implementation. If you&#8217;re not, have a look at <a href="https://docs.dataforseo.com/v3/" target="_blank" rel="noreferrer noopener">the documentation</a>. </li>



<li>This article is aimed at explaining the steps you have to follow. If you need help with the code, reach out to DataForSeo. </li>
</ul>



<p>To get the search volume, you need to follow these steps: </p>



<ul>
<li><strong>Pick a location: </strong>you have a specific endpoint to get the list. These locations are the same available in Google Ads and are usually a country, a region or a city. Once you&#8217;ve found yours, write it down. You can also use the <strong>location_name</strong>, but I prefer to use the latter. </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="886" height="616" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-11.png" alt="" class="wp-image-681" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-11.png 886w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-11-300x209.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-11-768x534.png 768w" sizes="(max-width: 886px) 100vw, 886px" /></figure>



<ul>
<li><strong>Divide your keywords in batch of up to 1000: </strong>One of the best features of this API is that you just need one call to get the data for 1,000 keywords. If you have 1,600 keywords, you need to send two calls: one with 1,000 keywords and another with 600. Using the POST method (<a href="https://docs.dataforseo.com/v3/keywords_data/google_ads/search_volume/task_post/?python" target="_blank" rel="noreferrer noopener">see documentation</a>) you then request the data. The data is not sent immediately unless you use the LIVE method (more expensive). <strong>Be careful to check the limitations you have for your keywords (forbidden characters, size) because if one of your keywords doesn&#8217;t pass the check, the full batch will fail. </strong></li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="415" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-12-1024x415.png" alt="" class="wp-image-685" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-12-1024x415.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-12-300x122.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-12-768x311.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-12.png 1154w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul>
<li><strong>Wait a moment and check that your data is available:</strong> based on the number of batches, you may have to wait &lt;1mn or way more. But there is <a href="https://docs.dataforseo.com/v3/keywords_data/google_ads/search_volume/tasks_ready/?python" target="_blank" rel="noreferrer noopener">an endpoint</a> to check if your data can be downloaded. </li>



<li><strong>Download your data: </strong>using the <a href="https://docs.dataforseo.com/v3/keywords_data/google_ads/search_volume/task_get/?python" target="_blank" rel="noreferrer noopener">last endpoint</a>, you can download what you wanted. The output (after a bit of data manipulation): the data you need! </li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="965" height="597" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-13.png" alt="" class="wp-image-687" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-13.png 965w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-13-300x186.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-13-768x475.png 768w" sizes="(max-width: 965px) 100vw, 965px" /></figure>



<h2 class="wp-block-heading">Conclusion </h2>



<p>Retrieving keyword volume at scale is sometimes required, as it helps you to understand the popularity of specific keywords. The method mentioned in this article is the one I use, and I always recommend. If you don&#8217;t know how to code, just ask someone who does and save time and money on the long term! </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/retrieve-search-volume-at-scale/">How to retrieve search volume at scale?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>What is content (re)hydration, and how can it impact your SEO?</title>
		<link>https://dev.aeripret.com/content-rehydration/</link>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Tue, 07 Feb 2023 14:53:36 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=654</guid>

					<description><![CDATA[<p>If you&#8217;re making a living as a SEO consultant, I&#8217;m sure you&#8217;ve heard of JavaScript SEO, a part of technical SEO that allows Javascript-powered websites to be easy for search engines to crawl, render and index. If that is not the case, have a look at this outstanding guide by the Onely&#8217;s team. As a [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/content-rehydration/">What is content (re)hydration, and how can it impact your SEO?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you&#8217;re making a living as a SEO consultant, I&#8217;m sure you&#8217;ve heard of JavaScript SEO, a part of technical SEO that allows Javascript-powered websites to be easy for search engines to crawl, render and index. If that is not the case, have a look at <a href="https://www.onely.com/blog/ultimate-guide-javascript-seo/" target="_blank" rel="noreferrer noopener">this outstanding guide by the Onely&#8217;s team</a>. </p>



<p>As a summary, trying to SEO a JavaScript-powered website usually means that you need to pay attention to two things: </p>



<ul>
<li>Ensure that search engines and users can see the same things. To achieve this objective, you&#8217;ll often rely on SSR (Server-Side Rendering) as CSR (Client-Side Rendering) can lead to longer rendering queue. </li>



<li>Ensure that key SEO components, such as links for instance, are not manipulated during the rendering process </li>
</ul>



<p>Sounds straight-forward, but it is not always as easy as it sounds. Moreover, there is a specific issue that is often caused by JavaScript frameworks that I would like to explain today. Because it is not well-documented and can potentially kill your SEO. And is linked to what is called content rehydration. </p>



<h2 class="wp-block-heading">What is content rehydration? </h2>



<p>Content rehydration is a process that occurs when a website, built with a JavaScript framework, such as Angular or React, dynamically updates the content on a page without requiring a full-page refresh. This means that when a user navigates to a new page, <strong>the core structure of the page is loaded first, and then the dynamic content is loaded in real-time, improving the loading speed and user experience.</strong> This process is called rehydration because it &#8220;brings the content back to life&#8221; after it has initially been loaded. In Angular, this is achieved through the use of server-side rendering and client-side rendering techniques, which work together to provide a fast and seamless experience for the user.</p>



<p>Why using rehydration instead of relying only on SSR? Simply because<strong> it cuts the number of operations you require your server to do before sending the response to the user, while ensuring that the application is interactive quickly. </strong></p>



<p>If you ensure that the main HTML components are included in your raw response sent by the server, we should be fine, shouldn&#8217;t we? Well, it&#8217;s not that simple. Let me tell you why. </p>



<h2 class="wp-block-heading">What is the issue with content rehydration? </h2>



<p>By default, this behavior comes with a major flaw, though. </p>



<p>It will add a script to the raw response sent by your server with all the required code to make the application dynamic. <strong>Out-of-the-box, this script can easily represent more than 90% of the total HTML size</strong>, as the example shown below from <a href="https://www.debugbear.com/html-size-analyzer" target="_blank" rel="noreferrer noopener">this tool.</a>  Nevertheless, 1.07&nbsp;MB (270kb compressed) is not something that should have that much impact, isn&#8217;t it? </p>



<p>I used to believe that. Boy, <strong>little did I know how wrong I was. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="443" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-1024x443.png" alt="" class="wp-image-662" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-1024x443.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-300x130.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-768x332.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-1536x664.png 1536w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-2048x886.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The issue is not linked to the size of the script itself, <strong>but what it means for search engines. </strong></p>



<p>Google explains clearly<a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics" target="_blank" rel="noreferrer noopener"> in its documentation</a> how it parses HTML document. The bottom-line is that once the raw response has been received, JavaScript files are executed to render the final HTML. <strong>The more JS code you have, the longer you&#8217;ll have to wait for your page to move from crawled to indexed. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="731" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-1-1024x731.png" alt="" class="wp-image-663" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-1-1024x731.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-1-300x214.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-1-768x548.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-1.png 1374w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>If you think from Google&#8217;s perspective, it makes complete sense. Indeed, this behavior rewards websites relying heavily on SSR, which decreases Google&#8217;s operating costs because parsing JavaScript at scale must be expensive. <strong>If you use out-of-the-box content rehydration, your website will take time to be indexed</strong> because every page will spend more time waiting in the rendering queue. </p>



<h2 class="wp-block-heading">What impact can it have during a migration? </h2>



<p>Scary, but what if I told you that there is a situation where it could actually be worse than that? Imagine that you are migrating from an old website to a Javascript-powered where you need to change your URL structure. Not an uncommon situation if you think about it. </p>



<p>Well, you&#8217;ll face the following situation in countless keywords: a sudden (but temporary) drop followed by a new position that is very likely to be lower than the previous one, at least in the short term. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="654" height="398" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-3.png" alt="" class="wp-image-666" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-3.png 654w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-3-300x183.png 300w" sizes="(max-width: 654px) 100vw, 654px" /></figure>



<p>Why you may ask? Let me explain: </p>



<ul>
<li>During the <strong>phase 1 </strong>(in my previous screenshot), we observe changes that are completely normal, as a result page can evolve daily</li>



<li>During the <strong>phase 2</strong>, a couple of days after the redirection is implemented, the position suddenly drops. </li>
</ul>



<p>The strange thing about this phase is that the ranked URL is neither the old URL nor the new one. It&#8217;s another one, usually not matching the user intent. My initial reaction was simple: Google is broken and is not handling redirections as well as it used to. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="400" height="320" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-4.png" alt="" class="wp-image-667" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-4.png 400w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-4-300x240.png 300w" sizes="(max-width: 400px) 100vw, 400px" /></figure>



<p>  Easier to blame a third party when the issue actually comes from your side, right? What was happening was actually simple: </p>



<ul>
<li>Google detected the 301 redirection and added the new URL in its crawl queue </li>



<li>Due to the content rehydration on the new pages, Google took forever to process the content </li>



<li>At some point, it removed the old URL from its index (respecting the 301) but as the new URL was not processed yet, it picked the closest matching URL for a while </li>



<li>When the new URL is finally processed, the new URL got back to (almost) the old rank </li>
</ul>



<p>The difference for the position between phase 2 &amp; 3 is explained by the difference in speed. While CWV are a tie-breaker, if your new page is significantly slower than the old one (content hydration will impact all CWV metrics but CLS) and given the resources Google will spend on your page, <strong>you&#8217;ll very likely not recover your initial rank. </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="806" height="268" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-5.png" alt="" class="wp-image-668" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-5.png 806w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-5-300x100.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-5-768x255.png 768w" sizes="(max-width: 806px) 100vw, 806px" /></figure>



<h2 class="wp-block-heading">How can you solve it? </h2>



<p>Content rehydration is not bad because it comes with major advantages I already explained at the beginning of the article. Nevertheless, out-of-the-box, the script includes a lot of information you are not using in your application. <strong>The trick is then to optimize it by asking your team to exclude the information you are not using. </strong></p>



<p>Too simple to be true? Well, that&#8217;s precisely what was done here and after the initial drop, script size was reduced by 50-75% and performance level went back to the initial level instantly. And rankings started to increase just after that.  </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="399" src="https://www.dev.aeripret.com/wp-content/uploads/2023/02/image-6-1024x399.png" alt="" class="wp-image-669" srcset="https://dev.aeripret.com/wp-content/uploads/2023/02/image-6-1024x399.png 1024w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-6-300x117.png 300w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-6-768x299.png 768w, https://dev.aeripret.com/wp-content/uploads/2023/02/image-6.png 1037w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Conclusion </h2>



<p>While having a huge script doesn&#8217;t necessarily affect only JavaScript-powered website, I&#8217;ve realized that this issue affects a lot of them, even it is often not detected until a migration happens, At the end of the day, we often look at the total HTML size, but not its breakdown by tag.</p>



<p> If you&#8217;re dealing with a JavaScript-powered website, make sure you know what content rehydration is and explain to your team how it needs to be optimized to avoid breaking your SEO. </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/content-rehydration/">What is content (re)hydration, and how can it impact your SEO?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Programmatic SEO: How to scale your traffic?</title>
		<link>https://dev.aeripret.com/programmatic-seo/</link>
					<comments>https://dev.aeripret.com/programmatic-seo/#respond</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Thu, 11 Aug 2022 12:28:35 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=618</guid>

					<description><![CDATA[<p>In March 2020, during the pandemic, I created two websites that are currently getting around 40,000 users per month through search engines. These projects were launched in a couple of hours, despite having thousands of pages. They indeed leverage what is called programmatic SEO, a smart way to scale your traffic. In this article, I&#8217;ll [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/programmatic-seo/">Programmatic SEO: How to scale your traffic?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In March 2020, during the pandemic, I created two websites that are currently getting around 40,000 users per month through search engines. These projects were launched in a couple of hours, despite having thousands of pages.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="414" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-6-1024x414.png" alt="" class="wp-image-620" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-6-1024x414.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-6-300x121.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-6-768x311.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-6.png 1369w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p> They indeed leverage what is called programmatic SEO, a smart way to scale your traffic. In this article, I&#8217;ll explain what you need to know about this concept that, even if you&#8217;re not familiar with the name, you already know. </p>



<h2 class="wp-block-heading">What is programmatic SEO? </h2>



<p>Programmatic SEO allows you to generate many pages using first or third-part data to generate the content. The definition is pretty straightforward. Let&#8217;s see one example with the project I created. If you&#8217;re curious, the URL is https://comoescribir.net/. </p>



<p>The website, in Spanish, helps you know how to correctly write numbers from 0 to 50,000. As you may have guessed, even if I managed to generate some traffic, this is not a project I could live from (I&#8217;m actually making around €50/month with it), so feel free to copy it if you like. <strong>The main template, used by 50,000 URL, leverages python libraries to provide the spelling for main numbers in several languages. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="996" height="1024" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-8-996x1024.png" alt="" class="wp-image-622" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-8-996x1024.png 996w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-8-292x300.png 292w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-8-768x790.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-8.png 1250w" sizes="(max-width: 996px) 100vw, 996px" /></figure>



<p>One myth I often read, and I&#8217;d like to debunk straight away: <strong><span style="text-decoration: underline;">programmatic SEO is not only designed for ranking on long-tail keywords</span></strong>. Big websites like Amazon, Skyscanner or Booking, to name just a few, rely extensively on programmatic SEO to build their architecture and generate traffic at scale. And let me tell you that these websites are not going after long-tail keywords <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<h2 class="wp-block-heading">How can I leverage programmatic SEO? </h2>



<p>Now that we&#8217;ve agreed on what programmatic SEO is, how can you use this information for your project? The theory is actually simple, but let&#8217;s use a real example Let&#8217;s assume that we are working for a train company in the UK and let me guide through the process. </p>



<h3 class="wp-block-heading">Find the head term</h3>



<p>Our objective is obviously to rank on keywords searches by users looking for a train ticket between two cities. <strong>You don&#8217;t have to be a genius to deduce that the main keyword will be &#8220;train london manchester&#8221; where London and Manchester can be replaced by any city</strong>. Even if you have variations for the same intent, it seems that this keyword is the most searched one. And very likely to convert well. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="357" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-9-1024x357.png" alt="" class="wp-image-625" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-9-1024x357.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-9-300x105.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-9-768x268.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-9.png 1318w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">Map the demand</h3>



<p>We have the search volume for one option, but we obviously want to map the full demand. In our example, it means: </p>



<ul><li><strong>Getting our hand on a complete list of UK cities</strong>. With more than 2,600 cities, we could use <a href="https://simplemaps.com/data/gb-cities">this database</a> for instance. Depending on the information you are looking for, you may have to build the database yourself, though. Not everything can be scraped. </li><li><strong>Build the keywords</strong>: with 2,600 cities in our database, we&#8217;d end up with more than 6M keywords. You don&#8217;t always have to handle that level of data, but be prepared to be surprise sometimes. </li><li><strong>Extract the volume</strong>: getting the search volume for more than 6M keyword is obviously not something you want to do manually.  You can do it quickly and in a cost-efficient way using https://dataforseo.com/. If I have more than 1,000 keywords, that&#8217;s the API I always end up using. </li></ul>



<p>At this stage, you&#8217;ll have a clear view of the demand and the 6M potential routes may be reduced to a couple of dozen of thousands. You may narrow down the list more by using first-party data like sales, obviously. </p>



<h3 class="wp-block-heading">Create a template</h3>



<p>If we look at a competitor, Trainline, we can see that they have pages equivalent to what we want to build. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="418" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-10-1024x418.png" alt="" class="wp-image-627" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-10-1024x418.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-10-300x123.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-10-768x314.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-10-1536x628.png 1536w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-10-2048x837.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Mapping the demand is easy, but now we need to build the pages. This means adding data regarding: </p>



<ul><li>Prices</li><li>Information about the journey </li><li>Information about the cities </li><li>Tricks to find cheap tickets </li><li>etc&#8230;. </li></ul>



<p><strong>It can get complex pretty fast</strong>, and you have to remind that the goal is to scale page creation; therefore you must avoid, as much as you can, manual content creation. Because it would be a nightmare to update and/or translate into another language. But even without manual content creation, these projects can get complex quick and involve several teams (SEO, Data, Back-end and Front-end).  </p>



<h3 class="wp-block-heading">Think about the internal linking </h3>



<p>Creating these pages you want to rank is not enough. In our example, we may end up with thousands of URLs, how are we going to feed them to Google? You have to find a relevant way to do so. </p>



<ul><li>Usually, the solution is to create an additional level in your architecture, not aimed at generating traffic but crucial for the internal linking</li><li>You can also leverage main navigation and links between pages </li></ul>



<p>For the project I mentioned at the beginning of the article, the internal linking mainly depends on links between pages, which are built using a <a href="https://audisto.com/guides/pagination/#ghostblock">ghostblock-like strategy</a>. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="490" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-11-1024x490.png" alt="" class="wp-image-629" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-11-1024x490.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-11-300x143.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-11-768x367.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-11-1536x734.png 1536w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-11-2048x979.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Conclusion </h2>



<p>Programmatic SEO is, in my opinion, one fancy term to explain what has been used for decades by companies to <strong>create &amp; rank pages at scale</strong>. Amazon or Walmart, to name just a few, are using this approach. Because it is necessary if you work on a project targeting a very heterogeneous demand. </p>



<p>The only question is: is it relevant for your project, and do you have the necessary data to achieve this approach? </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/programmatic-seo/">Programmatic SEO: How to scale your traffic?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/programmatic-seo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>What you&#8217;re not told about GSC data</title>
		<link>https://dev.aeripret.com/gsc-data/</link>
					<comments>https://dev.aeripret.com/gsc-data/#comments</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Mon, 08 Aug 2022 09:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=595</guid>

					<description><![CDATA[<p>Google Search Console is undoubtedly one of the most useful tools we have at our disposal to improve our performances. From the coverage report to the search result performances, the data at our disposal is wonderful. If you&#8217;re not using it, you&#8217;re seriously missing out. That being said, the data provided are often incomplete or [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/gsc-data/">What you&#8217;re not told about GSC data</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://search.google.com/search-console/about">Google Search Console</a> is undoubtedly one of the most useful tools we have at our disposal to improve our performances. From the coverage report to the search result performances, the data at our disposal is wonderful. If you&#8217;re not using it, you&#8217;re seriously missing out. </p>



<p>That being said, the data provided are often incomplete or incoherent. And it&#8217;s important to understand the data you are using as a basis for your recommendations, to avoid taking bad decisions. </p>



<h2 class="wp-block-heading">UI vs API</h2>



<p>Most of you are surely familiar with the UI. You may also know that for big projects, you shouldn&#8217;t rely too much on it. Several issues: </p>



<ul><li><strong>The UI won&#8217;t provide more than 1,000 rows</strong>, no matter what tab you are looking at. For instance, I have a property with more than 50,000 active URLs, and yet just 1,000 are shown. There is no workaround because even if you download the data using the &#8220;Export&#8221; button, you&#8217;ll be left with the same amount of data. </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="874" height="256" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image.png" alt="" class="wp-image-596" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image.png 874w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-300x88.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-768x225.png 768w" sizes="(max-width: 874px) 100vw, 874px" /></figure>



<ul><li>Filters have been improved since <a href="https://www.dev.aeripret.com/regex-seo/">RegEx</a> are supported, but it&#8217;s still <strong>impossible to apply more than one filter to the same dimension.</strong> </li><li><strong>You can&#8217;t resample data per week or month</strong>. I usually compare weekly or monthly data. Daily data are impractical. </li><li><strong>You can&#8217;t display results for combined dimensions</strong>. For instance, you can&#8217;t export a table including top URLs per country. The only way to achieve that using the UI would be to filter by country (one by one!) and export data individually.  </li></ul>



<p>So yes, the UI is easy to access, but you must be aware of the limitations. The API removes almost of all these limitations, and you can choose the best option to use it based on your knowledge and workflow: </p>



<ul><li><strong>Google Sheets: </strong>install <a href="https://searchanalyticsforsheets.com/">this add-on </a>and export your data directly into Google Sheets. A huge time-saver. Please note that you won&#8217;t be able to handle huge datasets, though, due to <a href="https://gsuitetips.com/tips/sheets/google-spreadsheet-limitations/">Google Sheets limitations</a>.</li><li> <strong>Python: </strong>Use <a href="https://github.com/joshcarty/google-searchconsole">this library,</a> which is simple and does the job perfectly. If you&#8217;ve never used it before, be sure to follow <a href="https://www.jcchouinard.com/searchconsole-api-wrapper-python/">this tutorial </a>on how to set it up. I&#8217;m sure there are equivalents in other languages, but I cannot guide you as I&#8217;m only using Python. </li><li><strong>Manual setup: </strong>You can also create your own program to access the API. Just head over to <a href="https://developers.google.com/webmaster-tools/v1/libraries">the official documentation</a> and try to follow the steps. </li></ul>



<h2 class="wp-block-heading">All dimensions are not equal</h2>



<p>If you&#8217;re familiar with GSC, you already know that you have five main dimensions: date, page, device, country &amp; query, The names are self-explanatory, and I don&#8217;t need to explain what they represent. </p>



<p>If you query one of these dimensions, and even if the API returns more than 1,000 rows, the total number of clicks &amp; impressions can vary. For instance, when I included the query dimension,<strong> I ended up with only 66% of the clicks and 29% of the impressions. </strong>The (main) official answer for these discrepancies are <a href="https://support.google.com/webmasters/answer/7576553?hl=en#by_query">anonymized queries</a>. Not quite sure about this one, but even with this percentage, Google Search Console is still your best keyword database. Please note that this percentage may be higher or lower in your case, as highlighted by a <a href="https://ahrefs.com/blog/gsc-hidden-terms-study/">recent study done by Ahrefs</a>. </p>



<p>You can also be surprised by why I have 120% of my impressions when I include the page dimension. <strong>The explanation is actually simple: more than one URL can be displayed for a given query (especially on your branded terms) and hence inflate impressions. </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="672" height="462" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-1.png" alt="" class="wp-image-599" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-1.png 672w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-1-300x206.png 300w" sizes="(max-width: 672px) 100vw, 672px" /></figure>



<p>Bottom line: do not forget that when you look at data grouped by query, <strong>you are actually looking at a sample. </strong>The rest seems accurate. </p>



<p>Now, what would happen if we combine two dimensions? I explained before that one of the main advantage of the API was to be able to combine dimensions. Does it impact the number of clicks and impressions returned as well? I created all possible combination and I ended up with the following results (see table below). </p>



<p>There are some obvious and not so obvious conclusions from this extraction. </p>



<ul><li>The order of the dimensions doesn&#8217;t matter. The results are the same if you retrieve the data for date/page or page/date. Might be obvious, but it has to be tested anyway. <strong> </strong></li><li>The second conclusion is the most counter-intuitive. When we retrieved data grouped by query, we got 66% (clicks) and 29% (impressions). When you add the page dimension, these values are increased to respectively 73% and 86%. <strong><span style="text-decoration: underline;">This makes no sense</span></strong>. <strong>The jump for the impressions can be explained by queries displaying more than one URL, but the jump for clicks has no logical explanation. </strong></li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="688" height="776" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-2.png" alt="" class="wp-image-600" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-2.png 688w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-2-266x300.png 266w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<h2 class="wp-block-heading">Using properties for your folders makes sense</h2>



<p>When Google announced the <a href="https://developers.google.com/webmaster-tools/v1/api_reference_index#Inspection_tools">URL inspection API</a> with a daily quota, one of the workarounds was to create one property by folder to increase it. Out of curiosity, I wanted to know if there was a discrepancy for clicks &amp; impressions for: </p>



<ul><li>The main property, keeping only data for a specific folder </li><li>The folder property </li></ul>



<p>There is indeed! <strong>While the number of clicks is the same, the impressions are slightly higher for the folder property.</strong> I&#8217;d say that the folder property is more likely to return more data because the dataset is smaller. If you need to calculate CTR, you better use folder property. <strong>Because in this example, it goes from 4.3% to 3.9%. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="317" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-3-1024x317.png" alt="" class="wp-image-602" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-3-1024x317.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-3-300x93.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-3-768x238.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-3-1536x476.png 1536w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-3.png 1608w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Be careful with the average position </h2>



<p>The average position in Google Search Console is often used to assess if a project is improving. It is sometimes useful: in the screenshot below, you can see that there is a correlation between the improvement in the average position and the CTR. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="402" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-4-1024x402.png" alt="" class="wp-image-606" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-4-1024x402.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-4-300x118.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-4-768x302.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-4-1536x603.png 1536w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-4-2048x804.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>That being said, the average position is something I&#8217;d ignore, for several reasons: </p>



<ul><li>In a world where Google is adding more and more element in its SERP (from ads to modules), <strong>your CTR depends more on your <a href="https://en.myposeo.com/blog/is-your-seo-ranking-accurate-enough-introducing-pixel-ranking/">pixel position </a>than anything else. Therefore, the position is almost irrelevant </strong></li><li>Your average position can actually decrease if your results improve. Imagine that you start to rank 9th on a generic query: <strong>your impressions would go up, but your position may go down. </strong></li><li>In some industries, Google displays a module highlighting results from comparators &amp; classified. Awesome, right? Well, not that much because any website including in this module will have a reported average position of 1 even if the CTR from this module is super low. Which, by the way, prevent you from building a relevant <a href="https://www.dev.aeripret.com/ctr-yield-curve/">CTR yield curve</a> with your data.  </li><li></li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="313" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/image-5-1024x313.png" alt="" class="wp-image-607" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/image-5-1024x313.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-5-300x92.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-5-768x235.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-5-1536x470.png 1536w, https://dev.aeripret.com/wp-content/uploads/2022/08/image-5.png 1720w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Conclusion</h2>



<p>As we&#8217;ve seen, there are some details that you better understand when you use Google Search Console to prevent you from taking bad decisions. Does it mean that data are bad? Not at all! As I highlighted in my introduction, Google Search Console is still one of the best (and free) SEO tools that you can use to improve your results. You just need to know how to interpret the data you are looking at <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> . </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/gsc-data/">What you&#8217;re not told about GSC data</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/gsc-data/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>RegEx (regular expressions) for SEO</title>
		<link>https://dev.aeripret.com/regex-seo/</link>
					<comments>https://dev.aeripret.com/regex-seo/#comments</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Thu, 04 Aug 2022 10:39:57 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=570</guid>

					<description><![CDATA[<p>Regular expressions &#8211; commonly referred to as RegEx &#8211; are a series of characters that are interpreted to identify patterns. They are supported by almost all programming languages and tools, such as Google Search Console or Screaming Frog, to name just a few. Sadly, RegEx can be intimidating and are seen as a tool that [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/regex-seo/">RegEx (regular expressions) for SEO</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Regular expressions &#8211; commonly referred to as RegEx &#8211; are a series of characters that are interpreted to identify patterns. They are supported by almost all programming languages and tools, such as Google Search Console or Screaming Frog, to name just a few. Sadly, RegEx can be intimidating and are seen as a tool that is powerful for data analysis but impossible to master for the majority. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="124" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/example-regular-expression-1024x124.png" alt="" class="wp-image-572" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/example-regular-expression-1024x124.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/example-regular-expression-300x36.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/example-regular-expression-768x93.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/example-regular-expression.png 1218w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Which I honestly believe because I&#8217;m yet to meet someone who would claim that. <strong>But you know what? It doesn&#8217;t matter.</strong> I use RegEx daily, and yet I master only the basics. Which is more than enough for most cases. </p>



<h2 class="wp-block-heading">Why and when should I care about RegEx? </h2>



<p>There are many skills that you can learn and apply in the search industry. Their usefulness depends on several factors, such as: </p>



<ul><li><strong>Your seniority level:</strong> I wouldn&#8217;t advise learning RegEx if you don&#8217;t master the basics of SEO. </li><li><strong>The kind of SEO you are</strong>: RegEx are more useful when you deal with a lot of data. If you make a living by creating niche websites, it may not be as useful as if you&#8217;re responsible for the SEO strategy at Walmart. </li></ul>



<p><strong>Don&#8217;t learn it just because you&#8217;ve liked this post, learn it because it could help you during your daily tasks. </strong></p>



<p>That being said, keep in mind that RegEx are supported by the vast majority of SEO tools such as Google Analytics, Google Search Console, Screaming Frog, Oncrawl and I could go on forever. You even have Google Sheets formulas (<a href="https://support.google.com/docs/answer/3098292?hl=es">REGEXMATCH</a> and <a href="https://support.google.com/docs/answer/3098244?hl=es">REGEXEXTRACT</a>) to use them directly in Excel. </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="233" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/google-analytics-regex-1024x233.png" alt="" class="wp-image-573" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/google-analytics-regex-1024x233.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-analytics-regex-300x68.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-analytics-regex-768x175.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-analytics-regex.png 1066w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>One smart example to leverage RegEx inside Google Search Console to pull questions around your brand. And I&#8217;ve used this trick in the past and it&#8217;s awesome to improve your FAQ / Customer support section. <strong>Without RegEx, pulling these questions would take more time. </strong></p>



<figure class="wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter"><div class="wp-block-embed__wrapper">
<blockquote class="twitter-tweet" data-width="550" data-dnt="true"><p lang="en" dir="ltr">Another piece of GSC regex magic by <a href="https://twitter.com/ChouinardJC?ref_src=twsrc%5Etfw">@ChouinardJC</a> <br><br>I asked him for something that could pull up question keyword PLUS the name(s) of the company &#8211; and below is the formula, voila! Case insensitive too. <br><br>(?i)^(who|what|where|when|why|how).*(brand|name).*<br><br>Thanks JC! <a href="https://t.co/xRToDJ9L6F">pic.twitter.com/xRToDJ9L6F</a></p>&mdash; Lily Ray <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f60f.png" alt="😏" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (@lilyraynyc) <a href="https://twitter.com/lilyraynyc/status/1417839743535140865?ref_src=twsrc%5Etfw">July 21, 2021</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div></figure>



<h2 class="wp-block-heading">RegEx 101</h2>



<p>As I already highlighted, in most cases, RegEx can actually be pretty simple. If you understand a few concepts, you&#8217;ll be able to use them with, maybe, a bit of Googling. </p>



<h3 class="wp-block-heading">dot (.) </h3>



<p>The dot is a wildcard that can match any character. It can be a letter, a number or a special character, it doesn&#8217;t matter. <strong>It&#8217;s not often used by itself, though: it usually comes with a quantifier that I&#8217;ll explain later in this article. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="337" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dot-1024x337.png" alt="" class="wp-image-575" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dot-1024x337.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dot-300x99.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dot-768x252.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dot.png 1165w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">circumflex accent (^)</h3>



<p>The circumflex accent indicates the beginning of a text we are trying to match. <strong>^apple</strong> only matches <strong>apple</strong> in my example below because the other word starts with the letter m. In plain English, <strong>this character will allow identifying strings that start with a specific text. </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="863" height="243" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-circumflex-accent.png" alt="" class="wp-image-576" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-circumflex-accent.png 863w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-circumflex-accent-300x84.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-circumflex-accent-768x216.png 768w" sizes="(max-width: 863px) 100vw, 863px" /></figure>



<h3 class="wp-block-heading">dollar sign ($) </h3>



<p>The dollar sign does the opposite of the circumflex accent: <strong>it allows identifying a string that ends with a specific text, </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="871" height="226" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dollar-sign.png" alt="" class="wp-image-577" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dollar-sign.png 871w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dollar-sign-300x78.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-dollar-sign-768x199.png 768w" sizes="(max-width: 871px) 100vw, 871px" /></figure>



<h3 class="wp-block-heading">backslash (\) </h3>



<p>As we are explaining, some characters have a special meaning when you are using them in a RegEx context. Now, what about matching these characters and not their meanings? How can we match a text including circumflex accents, for instance? You just have to use the backslash before them, <strong>whose purpose is to indicate that this character should be interpreted literally, and not with its meaning. </strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="259" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-backslash-1024x259.png" alt="" class="wp-image-578" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-backslash-1024x259.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-backslash-300x76.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-backslash-768x194.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-backslash.png 1163w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">several options</h3>



<p>Sometimes, you can have ore than one option to match a string. RegEx have several patterns to achieve just that: </p>



<ul><li><strong>[abc]</strong> indicates that you can have the letter a, b or c. Any of these options is fine. Example: </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="860" height="269" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-letter-brackets.png" alt="" class="wp-image-581" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-letter-brackets.png 860w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-letter-brackets-300x94.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-letter-brackets-768x240.png 768w" sizes="(max-width: 860px) 100vw, 860px" /></figure>



<ul><li><strong>[a-z]</strong> matches any lowercase letter from the Latin alphabet (<strong>if you speak spanish, ñ is not included)</strong>. </li><li><strong>[A-Z] </strong>does the same, but for capital letters </li><li><strong>[a-zA-Z] </strong>matches any letter, whether it is lowercase or capital </li><li><strong>[0-9] </strong>matches any digit </li></ul>



<p>It&#8217;s not that hard, is it? The only thing you&#8217;ll have to remember is how to type a bracket using your keyboard. Because it will be required quite often. </p>



<h2 class="wp-block-heading">Quantifier</h2>



<p>While the regular expressions we&#8217;ve seen in the previous sections will cover most of your cases, we still need to learn a key concept: the quantifier. <strong>These characters indicate how many times a specific string can be repeated. </strong></p>



<p>A quantifier is always applied to the character located before. In my example below, <strong>the plus sign (+), which mean 1 or more times, is applied to the letter o. </strong>Which is why it matches with the two first options, but not the third because it doesn&#8217;t include the letter o. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="856" height="224" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-plus-sign.png" alt="" class="wp-image-582" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-plus-sign.png 856w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-plus-sign-300x79.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-plus-sign-768x201.png 768w" sizes="(max-width: 856px) 100vw, 856px" /></figure>



<p>The main quantifiers are the following: </p>



<figure class="wp-block-table"><table><tbody><tr><td><strong>Character</strong></td><td><strong>Meaning</strong></td></tr><tr><td>*</td><td>0 or more times</td></tr><tr><td>+</td><td>1 or more times</td></tr><tr><td>?</td><td>0 or 1 time</td></tr><tr><td>{X}</td><td>X times</td></tr><tr><td>{X,Y}</td><td>Between X and Y times</td></tr></tbody></table></figure>



<p>For instance, <strong>we can match two folders, /flight/ and /flights/, with a single RegEx by indicating that the letter s is optional. </strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="855" height="220" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-question-mark-sign.png" alt="" class="wp-image-584" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-question-mark-sign.png 855w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-question-mark-sign-300x77.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-question-mark-sign-768x198.png 768w" sizes="(max-width: 855px) 100vw, 855px" /></figure>



<h2 class="wp-block-heading">Let&#8217;s practice: a simple example </h2>



<p>Let&#8217;s apply some of the concepts we&#8217;ve seen so far. Let&#8217;s imagine that you are working for a website selling plane tickets. Your web architecture includes thousands of URLs built following this pattern: </p>



<ul><li><strong>URL</strong>: https://www.example.com/flights-<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">barcelona</mark>&#8211;<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">paris</mark>_<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-purple-color">bar</mark>&#8211;<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-pale-pink-color">par</mark>.html</li></ul>



<ul><li><strong>Explanation</strong>: https://www.example.com/flights-{<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">origin</mark>}-{<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">destination</mark>}_{<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-purple-color">airport_origin</mark>}-{<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-pale-pink-color">airport_destination</mark>}.html</li></ul>



<p>From the explanation, we need to transform the variable into valid RegEx: </p>



<ul><li><strong>origin / destination</strong>: a word without space: <strong>[a-z]+</strong></li><li><strong>airport</strong>: three underscore letters:<strong> [a-z]{3}</strong></li></ul>



<p>Which will become: </p>



<p>https://www.example.com/flights-<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-luminous-vivid-orange-color">[a-z]+</mark>&#8211;<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">[a-z]+</mark>_<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-purple-color">[a-z]{3}</mark>&#8211;<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-pale-pink-color">[a-z]{3}</mark>.html</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="860" height="234" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/regex-example-practice.png" alt="" class="wp-image-585" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-practice.png 860w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-practice-300x82.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/regex-example-practice-768x209.png 768w" sizes="(max-width: 860px) 100vw, 860px" /></figure>



<p>That&#8217;s completely normal if it&#8217;s not (yet) easy to come up with custom RegEx quickly, but you&#8217;ll get there. As I highlighted, you may encounter specific cases where the RegEx I explained in this article are not enough, but you should be good for most cases. </p>



<h2 class="wp-block-heading">Let&#8217;s practice with a more complex example</h2>



<p>For this second example, let&#8217;s assume that we are working for <a href="https://animalear.com/">Animalear</a>, a Spanish E-commerce for pets. I&#8217;ve actually worked for this client when I started my professional career in Spain. It brings good memories back <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="666" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/animalear-homepage-1024x666.png" alt="" class="wp-image-587" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/animalear-homepage-1024x666.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/animalear-homepage-300x195.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/animalear-homepage-768x500.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/animalear-homepage.png 1505w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>We know that this client has a problem with some of its product URL. </p>



<ul><li>Some are user-friendly such as <a href="https://animalear.com/perros/scalibor/collar-scalibor-antiparasitario">https://animalear.com/perros/scalibor/collar-scalibor-antiparasitario</a></li><li>Other aren&#8217;t such as <a href="https://animalear.com/perros/nutro/p-195619">https://animalear.com/perros/nutro/p-195619</a></li></ul>



<p>You can obviously rank with both, but the former is better for several reasons. <strong>What we want to do is simple: check that we are not using the bad structure in our internal linking from key pages such as the homepage and categories. </strong></p>



<p>We could obviously perform this check manually, but if we have more than 10 pages, we&#8217;ll spend a significant amount of time. Let&#8217;s see how we can use RegEx to help us here. </p>



<ul><li>Let&#8217;s crawl the full website using Screaming Frog or any similar crawler </li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="258" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/crawl-screaming-frog-animalear-1024x258.png" alt="" class="wp-image-588" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/crawl-screaming-frog-animalear-1024x258.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/crawl-screaming-frog-animalear-300x76.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/crawl-screaming-frog-animalear-768x193.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/crawl-screaming-frog-animalear.png 1199w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul><li>We then export the inlinks report</li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="389" height="476" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/screaming-frog-export-inlinks.png" alt="" class="wp-image-590" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/screaming-frog-export-inlinks.png 389w, https://dev.aeripret.com/wp-content/uploads/2022/08/screaming-frog-export-inlinks-245x300.png 245w" sizes="(max-width: 389px) 100vw, 389px" /></figure>



<ul><li>We then open the file in Google Sheets. <strong>Please note that if the crawl is too big, you may not be able to open the file in Sheets. </strong></li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1015" height="515" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/inlinks-report-google-sheets.png" alt="" class="wp-image-591" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/inlinks-report-google-sheets.png 1015w, https://dev.aeripret.com/wp-content/uploads/2022/08/inlinks-report-google-sheets-300x152.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/inlinks-report-google-sheets-768x390.png 768w" sizes="(max-width: 1015px) 100vw, 1015px" /></figure>



<ul><li>We continue with our process: we are looking for links<strong> FROM categories TO non-optimized URLs. </strong>Categories have two different structures: https://animalear.com/pajaros/<strong>c_</strong>juguetes-para-pajaros&nbsp;and https://animalear.com/gatos/s_accesorios-casetas.<strong> In plain English: domain + a folder + the letter c or s followed by an underscore and the rest of the URL.  </strong>Which can be translated in REGEX; using the explanations given before, by <strong><span style="text-decoration: underline;">https:\/\/animalear.com\/.*\/[cs]_.*</span></strong></li><li>Applying the same logic with product URLs (such as <a href="https://animalear.com/perros/nutro/p-195619">https://animalear.com/perros/nutro/p-195619</a>), we define the RegEx<strong> https:\/\/animalear.com/.*/p-[0-9]+$</strong>. <strong>In English: domain + folders + the letter p + dash + some numbers </strong></li><li>Using the <a href="https://support.google.com/docs/answer/3098292?hl=es">REGEXMATCH</a> formula, we can filter our file to include only rows with the links we were looking for. </li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="365" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/google-sheets-result-analysis-regex-1024x365.png" alt="" class="wp-image-592" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/google-sheets-result-analysis-regex-1024x365.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-sheets-result-analysis-regex-300x107.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-sheets-result-analysis-regex-768x274.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/google-sheets-result-analysis-regex.png 1197w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Quicker than doing the whole thing manually, right? And we didn&#8217;t create complex RegEx. </strong></p>



<h2 class="wp-block-heading">Conclusion</h2>



<p>As I mentioned in my introduction, RegEx can be complex, but they often won&#8217;t be. By knowing your way around the basics, you&#8217;d be able to: </p>



<ul><li>Work smarter in Google Sheets </li><li>Leverage Custom Search &amp; Extraction in Screaming Frog or Oncrawl </li><li>Categorize your sections in Data Studio </li><li>And so much more </li></ul>



<p>I strongly advise taking some time to learn the basics if you are dealing with a lot of data. You don&#8217;t have to know everything, and you will save so much time in the long term that it will be worth it. </p>



<p></p>



<p></p>



<p></p>



<p></p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/regex-seo/">RegEx (regular expressions) for SEO</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/regex-seo/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Migration nightmare: how to define redirects at scale?</title>
		<link>https://dev.aeripret.com/redirects-at-scale/</link>
					<comments>https://dev.aeripret.com/redirects-at-scale/#comments</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Mon, 01 Aug 2022 08:59:41 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=565</guid>

					<description><![CDATA[<p>If you’ve been working in SEO for some times already, you must be familiar with web migrations. They often come with their doses of fun and unexpected complications, but they are part of what we do for a living.&#160; If handled poorly, a web migration can have a tremendous effect on online visibility and traffic. [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/redirects-at-scale/">Migration nightmare: how to define redirects at scale?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you’ve been working in SEO for some times already, you must be familiar with web migrations. They often come with their doses of fun and unexpected complications, but they are part of what we do for a living.&nbsp;</p>



<p>If handled poorly, a web migration can have a tremendous effect on online visibility and traffic. Which is obviously what you don’t desire to live through. That being said, a poorly handled migration is, unfortunately, one of the main reasons a business owner will start to take SEO seriously. When I was working agency-side, some of our clients started to work with us after a horrendous experience.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="385" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/visibility_index_impact_migration-1024x385.png" alt="" class="wp-image-564" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/visibility_index_impact_migration-1024x385.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/visibility_index_impact_migration-300x113.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/visibility_index_impact_migration-768x289.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/visibility_index_impact_migration.png 1479w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In such a situation, your number one priority when you onboard the client is to understand why the migration is having such an impact on traffic generation and how to fix it quickly. Often, redirects have been implemented poorly, and you need to start reviewing them. A very time-consuming, that can take, literally, days depending on the size of the project. This task is often handled to junior team members (I’ve been there) whose only purpose during days is to fill in an Excel file.&nbsp;</p>



<p>There are situations where it is the only situation because you cannot find any pattern to speed up your work. Fine, but often there are indeed better ways to proceed.&nbsp;</p>



<p>Let me walk through two quick and cost-efficient approaches you can take to save days of manual work to your team or yourself.&nbsp;</p>



<h2 class="wp-block-heading">The context&nbsp;</h2>



<p>To provide a real context to the article I’m writing, we’ll use the migration that happened some months ago for the French railway company. They decided to move from <a href="https://oui.sncf/">https://oui.sncf/</a> to <a href="https://www.sncf-connect.com/">https://www.sncf-connect.com/</a>.&nbsp;</p>



<p>The migration was actually handled correctly from an SEO perspective, but let’s assume that it wasn’t and that the company comes to us to try to fix it. They literally have thousands of URLs, so mapping them manually is not an option.&nbsp;&nbsp;</p>



<p>If you look at the position history, you’ll realize that the path (the URL stripped from the domain name) is actually the same, so we could handle this case with a simple redirect rule. But let’s assume &#8211; again, I know &#8211; that it’s not the case and that <strong>we have to find the equivalent, at scale.&nbsp;</strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="241" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/ahrefs_rank_history-1024x241.png" alt="" class="wp-image-557" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/ahrefs_rank_history-1024x241.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/ahrefs_rank_history-300x71.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/ahrefs_rank_history-768x181.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/ahrefs_rank_history.png 1501w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Option 1: use semantic proximity&nbsp;</h2>



<p>A web migration often comes with some URL changes (which is why you need redirects), but you frequently keep a similar structure. You may add some new words, remove some numbers, but there is a common ground.&nbsp;</p>



<p>This option leverages this and try to find the closest equivalent between our old &amp; new structures, by looking at the URL only. Why don’t we look at content?&nbsp;</p>



<ul><li>Old content may not be available anymore&nbsp;</li><li>Crawling a website takes time and if we can skip this step, we’ll be able to help our client faster&nbsp;</li></ul>



<p>Now, how the heck are we going to do it?&nbsp;</p>



<h3 class="wp-block-heading">Fuzzy matching&nbsp;</h3>



<p>If you don’t know what fuzzy matching is, you can read this <a href="https://lazarinastoy.com/ultimate-guide-to-fuzzy-string-matching-for-seo-with-google-sheets-template/#tf-idf-string-matching">amazing piece by Lazarina Stoy</a>, who explains everything.&nbsp;</p>



<p>As a summary:&nbsp;</p>



<ul><li>Fuzzy matching allows us to match similar strings. This is what Google uses to correct spelling mistakes, by the way:&nbsp;</li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="719" height="279" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/example_correction_google_search.png" alt="" class="wp-image-559" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/example_correction_google_search.png 719w, https://dev.aeripret.com/wp-content/uploads/2022/08/example_correction_google_search-300x116.png 300w" sizes="(max-width: 719px) 100vw, 719px" /></figure>



<ul><li>There are several algorithms available but in our case, we’d use the <a href="https://www.google.com/search?q=Levenshtein+distance&amp;oq=Levenshtein+distance&amp;aqs=chrome..69i57j0i512l9.271j0j7&amp;sourceid=chrome&amp;ie=UTF-8#:~:text=The%20Levenshtein%20Algorithm,the%2Dlevenshtein%2Dalgorit...">Levenshtein distance</a> (match words based on number of edits necessary to get from one word to the other) or <a href="https://es.wikipedia.org/wiki/Tf-idf">TF-IDF</a> which looks at how often a set of strings are repeated.&nbsp;</li></ul>



<p>I often use directly <a href="https://maartengr.github.io/PolyFuzz/">Polyfuzz</a> (python library) when I need to use fuzzy matching because it’s effortless to set up. Do not worry if you don’t need either Python or this library, I’ll explain everything and even provide a template.&nbsp;</p>



<h3 class="wp-block-heading">Our logic&nbsp;</h3>



<p>The logic we’ll code is actually pretty simple:&nbsp;</p>



<ol><li>We’ll first retrieve the two URLs lists we want to match (the old and the new one)&nbsp;</li><li>Run the fuzzy matching between them to define our redirects&nbsp;</li><li>Compare the output of our approach with the actual redirections the SNCF has in place to calculate the success rate&nbsp;</li></ol>



<p>We’ll try to match around 3,000 old URLs to a potential list of around 30,000. I’ve intentionally used a smaller number for the old URLs because the second option (that we’ll see in the next section) is not free, and I didn’t want to spend a lot of money on this explanation.&nbsp;</p>



<h3 class="wp-block-heading">The code</h3>



<p>You can find the code and output <a href="https://colab.research.google.com/drive/1B6dX6z6e2FD8_GW3LJARPAZ_PJ8e7Op3?usp=sharing">here</a>. Even if you don’t understand Python, you’ll see that it’s pretty straightforward.&nbsp;</p>



<p>We first the libraries we need for the script: <a href="https://pandas.pydata.org/">pandas</a> and <a href="https://pypi.org/project/polyfuzz/">polyfuzz</a>.&nbsp;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#load libraries
import pandas as pd
!pip install polyfuzz
from polyfuzz import PolyFuzz</code></pre></div>



<p>We then load our URLs lists (stored in an Excel file) in pandas DataFrames (which is an equivalent of a table in Python):</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#load urls lists
old = pd.read_excel(&#39;/content/drive/MyDrive/Website/Content/data_for_posts/redirects_at_scale.xlsx&#39;, sheet_name=&#39;old&#39;)
new = pd.read_excel(&#39;/content/drive/MyDrive/Website/Content/data_for_posts/redirects_at_scale.xlsx&#39;, sheet_name=&#39;new&#39;)</code></pre></div>



<p>We continue by converting our pandas DataFrames to regular Python lists (otherwise polyfuzz won’t work) and we launch the fuzzy matching.</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#convert to Python list (required by Polyfuzz)
old = old[&#39;URL&#39;].tolist()
new = new[&#39;URL&#39;].tolist()

#launch fuzzy matching
model = PolyFuzz(&quot;TF-IDF&quot;)
model.match(old, new)

#load results
result = model.get_matches()</code></pre></div>



<p>The output: our redirect mapping. The output is almost perfect (only 1.3% error our of almost 3,000 URLs) but please note that we are in an ideal situation here, where the old &amp; new structure are very alike.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="350" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/output_piolyfuzz-1024x350.png" alt="" class="wp-image-561" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/output_piolyfuzz-1024x350.png 1024w, https://dev.aeripret.com/wp-content/uploads/2022/08/output_piolyfuzz-300x103.png 300w, https://dev.aeripret.com/wp-content/uploads/2022/08/output_piolyfuzz-768x263.png 768w, https://dev.aeripret.com/wp-content/uploads/2022/08/output_piolyfuzz.png 1207w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>But still, this approach would save you a ton of time and would only require a manual check before its implementation. <strong>And you can generate it in less than 2 minutes.&nbsp;</strong></p>



<h2 class="wp-block-heading">Option 2: hey, Google! Do the work for me!&nbsp;</h2>



<p>The first approach is my favorite, but its output depends heavily on how close your URLs structures are. Likely, but not always the case.&nbsp;</p>



<p>The second option is more robust, but only works if the new content is indexed by Google, which means that you’ve been contacted only some days after the migration took place (and it may be too late). Also, it’s not free, although it’s not expansive either.&nbsp;</p>



<h3 class="wp-block-heading">Our logic&nbsp;</h3>



<p>Again, the logic is simple:&nbsp;</p>



<ol><li>We’ll first, using first or third-part data, extract the keyword that was generating the most traffic per URL</li><li>We’ll then, using the <strong>site: operator,</strong> get the most relevant result, according to Google, for this query&nbsp;</li></ol>



<p>For instance, <a href="https://www.theguardian.com/football/world-cup-2022">https://www.theguardian.com/football/world-cup-2022</a> is the most relevant URL for the keyword “world cup” for The Guardian.&nbsp;</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="716" height="300" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/example_query_theguardian.png" alt="" class="wp-image-560" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/example_query_theguardian.png 716w, https://dev.aeripret.com/wp-content/uploads/2022/08/example_query_theguardian-300x126.png 300w" sizes="(max-width: 716px) 100vw, 716px" /></figure>



<p>To retrieve data at scale, we’ll use <a href="https://www.valueserp.com/">ValueSERP</a>, a great SERP API.&nbsp;</p>



<h3 class="wp-block-heading">The code</h3>



<p>You can find the full code <a href="https://colab.research.google.com/drive/1FbjhHzqB5T8UmPKnFHc39IrRP819rtfI?usp=sharing">here</a>. This one is more complex than the previous one, and I’ll try to explain it fully.&nbsp;</p>



<p>The first part, as always, is intended as loading the libraries we’ll need for our script: <a href="https://pandas.pydata.org/">pandas</a>, <a href="https://requests.readthedocs.io/en/latest/">requests</a> and <a href="https://docs.python.org/3/library/json.html">json</a>. We also add a variable including our API key from ValueSERP. It can be found at the top of your dashboard.&nbsp;&nbsp;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#load libraries
import pandas as pd
import requests
import json

#valueserp key
api_key = ‘’</code></pre></div>



<p>We continue by loading the data from Semrush from our old website. In my example, I use third-part data, <strong>but if you can, I strongly advise using data from GSC. </strong>The logic being to end up with the most important keyword (in terms of traffic) by URL.&nbsp;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#load file containing keywords
kw = (
   pd
   .read_csv(&#39;/content/drive/MyDrive/Website/Content/data_for_posts/oui.sncf_keywords.csv&#39;)
   #keep only best kw by traffic
   .sort_values(by=&#39;Traffic&#39;, ascending=False)
   .drop_duplicates(&#39;URL&#39;, keep=&#39;first&#39;)
   #add column with keyword to use in Google
   .assign(Keyword_Google = lambda df:&#39;site:sncf-connect.com &#39;+df.Keyword)
)

#remove useless columns
kw = kw[[&#39;Keyword&#39;,&#39;URL&#39;,&#39;Keyword_Google&#39;]]</code></pre></div>



<p>At this stage, we have a DataFrame containing the <strong>Google query that we’ll use to find the redirect per URL.</strong></p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="742" height="208" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/dataset_after_step_1.png" alt="" class="wp-image-558" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/dataset_after_step_1.png 742w, https://dev.aeripret.com/wp-content/uploads/2022/08/dataset_after_step_1-300x84.png 300w" sizes="(max-width: 742px) 100vw, 742px" /></figure>



<p>We now can send the information to ValueSERP. I won’t explain in-depth this part because it is based on the <a href="https://www.valueserp.com/docs">documentation</a>, but if you want to use my code, you’d need:&nbsp;</p>



<ul><li>To create more than one batch, because you cannot have more than 1,000 queries per batch. I decided to limit my extraction at 1,000 to reduce the cost associated to this article&nbsp;</li><li>Update the location, google domain, device …. based on your project&nbsp;</li></ul>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#list of queries to send to ValueSERP
#in this example, I&#39;m just getting the data for 1000 keywords
kws = kw[&#39;Keyword_Google&#39;].head(1000).tolist()

location = &#39;France&#39;
google_domain = &#39;google.fr&#39;
gl = &#39;fr&#39;
hl = &#39;fr&#39;
device = &#39;desktop&#39;
num = 20
      
param_list = []

#create a list of parameters for each set
for i in range(0, len(kws)):
 param_list.append({
 &#39;api_key&#39;: api_key,
 &#39;q&#39;: kws[i],
 &#39;location&#39;: location,
 &#39;google_domain&#39;: google_domain,
 &#39;gl&#39;:gl,
 &#39;hl&#39;:hl,
 &#39;device&#39;:device,
 &#39;num&#39;: str(int(num))
    
 })
      

#create our batch
body = {
&quot;name&quot;:&#39;Demo_SNCF_Connect&#39;,
&quot;enabled&quot;: True,
&quot;schedule_type&quot;: &quot;manual&quot;,
&quot;priority&quot;: &quot;normal&quot;,
&quot;searches_type&quot;:&quot;web&quot;
}

#create batch
api_result = requests.post(f&#39;https://api.valueserp.com/batches?api_key={api_key}&#39;, json=body)
api_response = api_result.json()
#get id
batch_id  = api_response[&#39;batch&#39;][&#39;id&#39;]

#send data to batch
for i in range(0, len(param_list)):
   body = {&quot;searches&quot;:[]}
   for param in param_list:
       body[&quot;searches&quot;].append(param)

api_result = requests.put(f&#39;https://api.valueserp.com/batches/{batch_id}?api_key={api_key}&#39;, json=body).json()</code></pre></div>



<p>Once this code is executed, you’ll have a batch created in ValueSERP and you can (manually) launch the extraction:</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="734" height="381" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/start_batch_valueserp.png" alt="" class="wp-image-563" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/start_batch_valueserp.png 734w, https://dev.aeripret.com/wp-content/uploads/2022/08/start_batch_valueserp-300x156.png 300w" sizes="(max-width: 734px) 100vw, 734px" /></figure>



<p>It is usually superfast (&lt;1mn) and once it has finished, you can copy the batch ID to execute the rest of the code, which basically retrieve the information provided by ValueSERP, apply some filtering to get the desired data.</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#required parameters
params = {
 &#39;api_key&#39;: api_key,
 &#39;page_size&#39;:1000
}
batch_id = &#39;&#39;

#get results for ou batches
results = pd.DataFrame()
#get batch info
api_result = requests.get(f&#39;https://api.valueserp.com/batches/{batch_id}/results/1/csv&#39;, params=params)
for url_csv in api_result.json()[&#39;result&#39;][&#39;download_links&#39;][&#39;pages&#39;]:
 results = results.append(pd.read_csv(url_csv), ignore_index=True)

#get results for ou batches
results = pd.DataFrame()

#get batch info
api_result = requests.get(f&#39;https://api.valueserp.com/batches/3F3A94E1/results/1/csv&#39;, params=params)
for url_csv in api_result.json()[&#39;result&#39;][&#39;download_links&#39;][&#39;pages&#39;]:
 results = results.append(pd.read_csv(url_csv), ignore_index=True)

#keep only top URL
results_filtered = results[results[&#39;result.organic_results.position&#39;]==1]
#remove unsucessful scrape
results_filtered = results_filtered[results_filtered[&#39;success&#39;]==True]
#keep only useful columns
results_filtered = results_filtered[[&#39;search.q&#39;,&#39;result.organic_results.link&#39;]]
#use more standard names
results_filtered.columns = [&#39;Keyword_Google&#39;,&#39;Redirect_URL&#39;]
results_filtered.head()
</code></pre></div>



<p>We end up with a DataFrame including our query and the best result, according to Google. We can now merge this data to our initial table, like we did when we were using Polyfuzz.</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="574" height="209" src="https://www.dev.aeripret.com/wp-content/uploads/2022/08/output_value_serp.png" alt="" class="wp-image-562" srcset="https://dev.aeripret.com/wp-content/uploads/2022/08/output_value_serp.png 574w, https://dev.aeripret.com/wp-content/uploads/2022/08/output_value_serp-300x109.png 300w" sizes="(max-width: 574px) 100vw, 574px" /></figure>



<p>The output is actually worse than our previous solution (8.6% of error) but it is still a quite satisfactory result. Imagine the number of hours saved anyway!&nbsp;</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p>Redirections can be time-consuming, but if you use any of these two options to speed up your work, you can deliver more value to your client in a matter of hours than a full team doing manual work in days. And you can then spend more time on tasks that cannot be automatized!&nbsp;</p>



<p>Work smarter, not harder <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/redirects-at-scale/">Migration nightmare: how to define redirects at scale?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/redirects-at-scale/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Build a Chrome extension to automate a part of your routine</title>
		<link>https://dev.aeripret.com/create-chrome-extension/</link>
					<comments>https://dev.aeripret.com/create-chrome-extension/#respond</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Tue, 03 Aug 2021 12:34:35 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=465</guid>

					<description><![CDATA[<p>If you are using Google Chrome, installing extensions is often a good way to improve your workflow and work more efficiently. To name just one, I really love the Robots exclusion checker which enables you to visually check if a page is indexable when you browse a website. Before using it, checking the canonical tag, [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/create-chrome-extension/">Build a Chrome extension to automate a part of your routine</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you are using Google Chrome, installing extensions is often a good way to improve your workflow and work more efficiently. To name just one, I really love the <a href="https://chrome.google.com/webstore/detail/robots-exclusion-checker/lnadekhdikcpjfnlhnbingbkhkfkddkl" target="_blank" rel="noreferrer noopener">Robots exclusion checker</a> which enables you to visually check if a page is indexable when you browse a website. Before using it, checking the canonical tag, the meta robots, the robots.txt file &amp; the HTTP header was quite time-consuming, even if we obviously have tools (like <a href="https://www.screamingfrog.co.uk/seo-spider/" target="_blank" rel="noreferrer noopener">Screaming Frog</a>) to speed up the process.  </p>



<p>Great, but what if your need is not covered by an existing extension? In today’s post, I want to explain <strong>how to create &amp; publish a Chrome extension from scratch from a newbie (myself) point of view. </strong></p>



<h2 class="wp-block-heading">The idea&nbsp;</h2>



<p>As most SEO professionals, I’m often using Google Search Console for my projects. When it comes to keywords data and even if the query report is kind of sampled, the tool gives us the most comprehensive data we can find. Nevertheless, when I’m performing keyword research for new content ideas or to improve my existing contents, the typical process is the following:</p>



<ul><li>Analyze data from GSC UI or using the API </li><li>Extract search volume using a third party tool (Google Ads, Semrush….)  </li><li>Merge everything using Excel or Sheets </li></ul>



<p>But<strong> isn’t there a way of displaying the search volume directly within the GSC API</strong>? It would cut down the time spent on this task significantly because all data would be unified in the same UI. </p>



<p>When I realized that no other extension (that I knew of) offered this functionality, I decided to build my own.&nbsp;</p>



<h2 class="wp-block-heading">How does a Chrome extension work?&nbsp;</h2>



<p>If you don’t know it yet, Chrome extensions rely heavily on JavaScript. Based on the complexity of the one you want to create, you may need several files to make  work, but you need at least: </p>



<ol><li><strong>manifest.json</strong>: a file including several key information on the extension, like its name, its description and when it should be executed once installed. For more information, <a href="https://developer.chrome.com/docs/extensions/mv2/manifest/">please see the official documentation</a>.&nbsp;</li><li><strong>code.js</strong>: the name can be different (you specify it in your manifest.json file), but this JavaScript file includes the code to be executed by your extension. In my case, all the instructions are there.&nbsp;</li></ol>



<p>If you are curious to know how your favorite extensions work, you can follow <a href="https://www.howtogeek.com/198964/how-to-view-the-source-code-of-a-chrome-extension/">this tutorial.&nbsp;</a></p>



<h2 class="wp-block-heading">How does my extension work?&nbsp;</h2>



<p>If you use <a href="https://chrome.google.com/webstore/detail/keyword-volume-extractor/fhabpopknhenifjggdimpmelijmgohig">the extension</a>, you can do two things:&nbsp;</p>



<ul><li>Display search volume directly in GSC UI&nbsp;</li><li>Add a new download button to download these volumes (if you want) along with your keywords data&nbsp;</li></ul>



<p>The full code is available in my <a href="https://github.com/antoineeripret/google_search_console_search_volume/tree/main/code">GitHub repository</a>, but let me explain how it works piece by piece. </p>



<h3 class="wp-block-heading">JavaScript vs Python&nbsp;</h3>



<p>If you’ve read other articles I’ve published on my blog, you may know that I’m regularly using Python to speed-up my work. In other words: <strong>JavaScript is not my thing</strong>. Therefore, how the heck did I create an extension based on this language?</p>



<ul><li><strong>Collaboration</strong>: the SEO industry is great. If you ask for help, there is always someone willing to help you. In my case, I spoke with <a href="https://twitter.com/jlhernando">Jose Luis Hernando </a>who’s more than knowledgeable on JS and he helped me a lot through the project. I also asked <a href="https://twitter.com/_fede_gomez_">Fede Gomez</a> to solve a specific issue I had at the end of the project, which I will explain later.&nbsp;</li><li><strong>Knowledge transfer</strong>: even if JavaScript has some key differences with Python, some concepts are basically the same. You just have to Google quickly how to write a loop, but you don’t have to understand the logic because you already learned it before.&nbsp;</li></ul>



<h3 class="wp-block-heading">The Logic </h3>



<p>The logic followed by the extension is pretty simple:</p>



<ol><li>Download keywords from GSC UI</li><li>Extract their volumes from an API </li><li>Add them into the UI </li></ol>



<p><strong>Download keywords from GSC&nbsp;</strong></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-lang="JavaScript"><code>function get_keywords() {
  //array where we will store our keyword
  let arr = [];
  //gets table
  //there are several tables with the same class
  var oTable = document.getElementsByClassName(&#39;i3WFpf&#39;)[0];
  //gets rows of table
  var rowLength = oTable.rows.length;
  //loops through rows
  for (i = 0; i &lt; rowLength; i++) {
    //gets cells of current row
    var oCells = oTable.rows.item(i).cells;
    //loops through each cell in current row{
    arr.push(oCells.item(0).innerText);
  }
  // Remove &quot;Top Queries&quot; header from array
  arr.shift();
  return arr;
}</code></pre></div>



<p>The data we want to extract is included in a table whose class is &#8220;i3WFpf&#8221;. I really hope that Google won&#8217;t change that soon, otherwise my extension will need to be updated!  </p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="496" src="https://www.dev.aeripret.com/wp-content/uploads/2021/07/image-1024x496.png" alt="" class="wp-image-468" srcset="https://dev.aeripret.com/wp-content/uploads/2021/07/image-1024x496.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-300x145.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-768x372.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-1536x743.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/07/image.png 1866w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>To extract the keywords, I loop through this table and extract the first row (the keyword). <strong>I end up with an array (a list) of up to 1.000 keywords, which is GSC&#8217;s UI limit. </strong>This step was pretty easy because I honestly believed that the source code was going to be messier, like in Amazon for instance. Fortunately, that was not the case at all. </p>



<p><strong>Extract their volumes from an API</strong></p>



<p>Before even explaining the code, I want to highlight which API I used and why. My extension uses Keyword Surfer&#8217;s (that you may know for their <a href="https://chrome.google.com/webstore/detail/keyword-surfer/bafijghppfhdpldihckdcadbcobikaca">Keyword Surfer extension</a>) because: </p>



<ul><li>It is free </li><li>It doesn&#8217;t require neither authentification nor API key (at least now) </li><li>It allows bulk request: with one request, you can retrieve the volume for up to 50 keywords.</li></ul>



<p>The last advantage was decisive, because having to execute up to 1.000 requests (for up to 1.000 keywords) would have impacted A LOT the speed at which the extension can retrieve search volumes. <strong>It also means that I won&#8217;t surcharge their API with my extension, which is the last thing I want. </strong></p>



<p>That being said, the API is far from perfect: </p>



<ul><li>Some volumes are not updated very often </li><li>Low-volume keywords are badly reported </li><li>As usual, keywords are grouped. &#8220;Buy car&#8221; and &#8220;buy cars&#8221; will return the same volume (or one of them will return 0) when we do have separate metrics in GSC. This is a very known limitation for most API though, not only Keyword Surfer&#8217;s. </li><li>If the Keyword Surfer doesn&#8217;t have data for a specific keyword, it doesn&#8217;t return 0 but noting. Something we had to take into account in our logic. </li></ul>



<p>That being said, let&#8217;s have a look at the code: </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-lang="JavaScript"><code>async function get_search_vol(chunk, url) {
  const requestUrl = url; // URL to request
  const response = await fetch(requestUrl); // Make request to Keyword Surfer
  const json = await response.json(); // Transform response to JSON
  //loop the response and return an array with volumes
  let keywords = {};
  for (i = 0; i &lt; chunk.length; i++) {
    keywords[chunk[i]] = json[chunk[i]]?.search_volume ?? 0; // If keyword has data get the data else return 0 (optional chaining operator (?.) + Nullish coalescing operator (??))
  }
  return keywords;
}

//function to divide an array in X arrays
//Source: https://ourcodeworld.com/articles/read/278/how-to-split-an-array-into-chunks-of-the-same-size-easily-in-javascript
function chunkArray(myArray, chunk_size) {
  var index = 0;
  var arrayLength = myArray.length;
  var tempArray = [];

  for (index = 0; index &lt; arrayLength; index += chunk_size) {
    var myChunk = myArray.slice(index, index + chunk_size);
    // Do something if you want with the group
    var finalChunk = [];
    for (y = 0; y &lt; myChunk.length; y++) {
      finalChunk.push(myChunk[y].replace(&#39;&quot;&#39;, &#39;&#39;).replace(&#39;&quot;&#39;, &#39;&#39;).replace(&#39;&&#39;,&#39;&#39;));
    }
    tempArray.push(finalChunk);
  }

  return tempArray;
}

//generate the Keyword Surfer&#39;s URLs for our chunks
function generate_urls(chunks, country) {
  //output
  const arr = [];

  //Base URL to generate our list of urls
  const base_url = `https://db2.keywordsur.fr/keyword_surfer_keywords?country=${country}&keywords=[%22`;

  // Loop through chunk to create array of keywords in request
  for (i = 0; i &lt; chunks.length; i++) {
    var url = base_url.concat(chunks[i].join(&#39;%22,%22&#39;), &#39;%22]&#39;);
    arr.push(url);
  }
  return arr;
}</code></pre></div>



<p>To extract volumes, there are several steps: </p>



<ul><li>A function called <strong>chunkArray</strong> to divide our list of keywords in chunks of X elements. We need to perform that action because we can request up to 50 keywords to Keyword Surfer&#8217;s API. </li><li>A function called <strong>generate_urls</strong> to create a list of requests URLs using our chunks. The structure is pretty simple: https://db2.keywordsur.fr/keyword_surfer_keywords?country=us&amp;keywords=[%22buy%20car%22,%22buy%20cars%22] (example with 2 keywords), even if special characters (see this <a href="https://github.com/antoineeripret/google_search_console_search_volume/issues/7">reported issue in GitHub</a>) can break the API and are currently the main reason why users can&#8217;t use the extension properly sometimes. </li><li>A function called <strong>get_search_vol</strong> to retrieve the search volume. Indeed, Keyword Surfer&#8217;s API provides us with more than just the search volume, we therefore need to process the result to extract only the information we need. </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="524" height="929" src="https://www.dev.aeripret.com/wp-content/uploads/2021/07/image-1.png" alt="" class="wp-image-469" srcset="https://dev.aeripret.com/wp-content/uploads/2021/07/image-1.png 524w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-1-169x300.png 169w" sizes="(max-width: 524px) 100vw, 524px" /></figure>



<p>We then add these functions altogether inside another one to obtain the expected output: a list of keywords (from GSC) with their associated volume (from Keyword Surfer&#8217;s API). </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-lang="JavaScript"><code>
async function getData(country) {
  const kws = get_keywords(); // Get all keywords from GSC
  const chunks = chunkArray(kws, 50); // transform keyword in multiple arrays of 50 keywords
  const urls = generate_urls(chunks, country); // Build Request URL
  const allKeywords = {}; // Store future reponses in hashmap

  // Loop through GSC set of keywords and request keywoFrd surfer data
  for (let i = 0; i &lt; urls.length; i++) {
    var sv = await get_search_vol(chunks[i], urls[i]);
    var keys = Object.keys(sv);
    for (let y = 0; y &lt; keys.length; y++) {
      allKeywords[Object.keys(sv)[y]] = sv[Object.keys(sv)[y]];
    }
  }
  // console.log(allKeywords); // Just to check the output
  return allKeywords;
}</code></pre></div>



<p>Amazing! The only this we now need is to display this information to the user! </p>



<p><strong>Add them to the UI</strong></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-lang="JavaScript"><code>function createCell(text) {
  var cell = document.createElement(&#39;td&#39;);
  var cellText = document.createTextNode(text);
  cell.appendChild(cellText);
  cell.setAttribute(
    &#39;style&#39;,
    &#39;font-size:12px;font-weight:bold;text-align:center;padding:18px 28px;&#39;
  );
  return cell;
}

async function addVolumes(country) {
  const volumes = await getData(country); // Wait to get hasmap of search volumes
  const tbl = document.getElementsByClassName(&#39;i3WFpf&#39;)[0]; // Select table
  // Future CSV
  let csvExport = &#39;&#39;;

  // Loop through rows
  for (let i in tbl.rows) {
    // For some reason there is an undefined row at the end
    if (i === &#39;length&#39;) {
      break;
    } else {
      // Select each row
      let row = tbl.rows[i];
      // Create line for future CSV
      const line = [];
      // Select first cell (query)
      const query = row.cells[0].textContent;
      // Add header
      if (query === tbl.rows[0].cells[0].textContent) {
        row.appendChild(createCell(&#39;Search Volumes&#39;));
      } else {
        // If there is search volume data add it
        if (volumes[query]) row.appendChild(createCell(volumes[query]));
        // If not add 0 search volume
        else row.appendChild(createCell(0));
      }
      // Loop through cells
      for (const cell of row.cells) {
        const text = cell.textContent;
        line.push(text.replace(/,/g, &#39;&#39;));
      }
      // Create each filled line for future CSV
      csvExport = csvExport.concat(line.join(&#39;,&#39;), &#39;\n&#39;);
    }
  }
  createDownloadButton(csvExport);
}</code></pre></div>



<p>The logic is pretty simple here: </p>



<ul><li>A function called <strong>createCell</strong> is used to create cells in our GSC table. The CSS added (inline) in this function can be seen in your source code once the volumes have been retrieved by the extension. </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="990" height="57" src="https://www.dev.aeripret.com/wp-content/uploads/2021/07/image-2.png" alt="" class="wp-image-470" srcset="https://dev.aeripret.com/wp-content/uploads/2021/07/image-2.png 990w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-2-300x17.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/07/image-2-768x44.png 768w" sizes="(max-width: 990px) 100vw, 990px" /></figure>



<ul><li>A function called <strong>addVolumes</strong> add the volume column to our entire table. If the keyword has no data in Keyword Surfer (as mentioned above), we add 0 by default. </li></ul>



<h3 class="wp-block-heading">What about user input?</h3>



<p>If you used my extension, you remember that you need to specify your country to use it. </p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="592" height="184" src="https://www.dev.aeripret.com/wp-content/uploads/2021/08/image-2.png" alt="" class="wp-image-474" srcset="https://dev.aeripret.com/wp-content/uploads/2021/08/image-2.png 592w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-2-300x93.png 300w" sizes="(max-width: 592px) 100vw, 592px" /></figure>



<p>Storing an user input to be reused later on was actually the most difficult part of the whole process. The code may look simple but: </p>



<ul><li>The <a href="https://developer.chrome.com/docs/extensions/reference/storage/">documention </a>is not very clear and understanding why your code doesn&#8217;t work is not easy either, because it often doesn&#8217;t trigger any error <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f641.png" alt="🙁" class="wp-smiley" style="height: 1em; max-height: 1em;" />  </li><li>Google Chrome has its own standards instead of using JavaScript&#8217;s </li></ul>



<p>That being said, the following code allows my extension to save the user input, to be later on used by the main code to retrieve search volume for a given country. In my example, it would retrieve volume for France. </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-js" data-lang="JavaScript"><code>const optionsStatus = document.getElementById(&#39;status&#39;);
const countryList = document.getElementById(&#39;country&#39;);
const optionsSaveBtn = document.getElementById(&#39;save&#39;);

function save_options() {
  var country = document.getElementById(&#39;country&#39;).value;
  chrome.storage.local.set({ country: country }, function () {
    // Update status to let user know options were saved.
    var status = document.getElementById(&#39;status&#39;);
    status.textContent = &#39;Options saved.&#39;;
    setTimeout(function () {
      status.textContent = &#39;&#39;;
    }, 750);
  });
}

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
  // Use default value color = &#39;red&#39; and likesColor = true.
  chrome.storage.local.get(
    {
      country: &#39;fr&#39;,
    },
    function (items) {
      document.getElementById(&#39;country&#39;).value = items.country;
    }
  );
}
document.addEventListener(&#39;DOMContentLoaded&#39;, restore_options);
document.getElementById(&#39;save&#39;).addEventListener(&#39;click&#39;, save_options);</code></pre></div>



<h2 class="wp-block-heading">How to debug an extension? </h2>



<p>When you create a JavaScript code to be used by an extension, you need to test &amp; debut it first. You have two options: </p>



<ul><li><strong>Use the code directly in Chrome</strong>: you can run directly JavaScript using the Console tab of Chrome DevTools. Pretty handy at the beginning (because you don&#8217;t have to install anything) but it forces you to copy-paste your code quite often. </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="910" height="309" src="https://www.dev.aeripret.com/wp-content/uploads/2021/08/image.png" alt="" class="wp-image-472" srcset="https://dev.aeripret.com/wp-content/uploads/2021/08/image.png 910w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-300x102.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-768x261.png 768w" sizes="(max-width: 910px) 100vw, 910px" /></figure>



<ul><li><strong>Install your extension</strong>: you can install an unverified extension (follow <a href="https://stackoverflow.com/questions/24577024/install-chrome-extension-form-outside-the-chrome-web-store">these steps</a>) and use it as a regular user would. It requires you to build the manifest.json though, something you may not want to create at the beginning of the process. </li></ul>



<p>Maybe there are most efficient way to debug an extension, but as a newbie I only used these two, and it was more than enough for my project. </p>



<h2 class="wp-block-heading">How to submit your extension? </h2>



<p>If you want to publish your extension, you can follow the <a href="https://developer.chrome.com/docs/webstore/publish/">official documentation</a> which explains it pretty well. To summarize the process, you&#8217;ll have: </p>



<ul><li>To create a developer account and pay the 5$ fee (even if you extension is free) </li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="526" src="https://www.dev.aeripret.com/wp-content/uploads/2021/08/image-3-1024x526.png" alt="" class="wp-image-475" srcset="https://dev.aeripret.com/wp-content/uploads/2021/08/image-3-1024x526.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-3-300x154.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-3-768x394.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-3.png 1509w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ul><li>Submit your extension by providing the required information, like its purpose. Please note that you need to justify the <a href="https://developer.chrome.com/docs/extensions/mv3/declare_permissions/">permission </a>required by your extension. I actually got my second submission rejected because I required too many permissions when it was not necessary. So be careful with that, because it will slow the full process. </li><li>Don&#8217;t include the word &#8220;Google&#8221; in your extension name (or any other brand name). My first rejection was caused by this rookie mistake, Google considering that you pretend to be someone else (Google in my case) by using its name. </li></ul>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="618" height="411" src="https://www.dev.aeripret.com/wp-content/uploads/2021/08/image-4.png" alt="" class="wp-image-476" srcset="https://dev.aeripret.com/wp-content/uploads/2021/08/image-4.png 618w, https://dev.aeripret.com/wp-content/uploads/2021/08/image-4-300x200.png 300w" sizes="(max-width: 618px) 100vw, 618px" /></figure>



<p>Apart from these recommandations, the validation process is rather quick (less than a week). If you want to update your extension, the process is even quicker as you obviously don&#8217;t need to fill everything again. </p>



<h2 class="wp-block-heading">Conclusion</h2>



<p>Improving my workflow is always one of my priority and despite coming from a Python background, developing this extension was not as hard as I thought. I wouldn&#8217;t have achieved it without the help &amp; advice of the persons I mentioned at the beginning of the article though, let&#8217;s be honest here <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /><strong> Extensions are easy to create and can save us a lot of time, so that&#8217;s definitely something I&#8217;ll be looking at if you know a bit of JavaScript. </strong></p>



<p>Python is popular right now, but we can&#8217;t deny that JavaScript has its advantages. </p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/create-chrome-extension/">Build a Chrome extension to automate a part of your routine</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/create-chrome-extension/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Scrape Google Suggest at scale</title>
		<link>https://dev.aeripret.com/scrape-google-suggest/</link>
					<comments>https://dev.aeripret.com/scrape-google-suggest/#comments</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Tue, 04 May 2021 14:01:59 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=461</guid>

					<description><![CDATA[<p>If you work as an SEO consultant, one of your missions is to understand how the market evolves in order to adapt your strategy. Why? Because it is easier to get results if we get there first and we must always adapt to the changing behavior of our customers. There are several tools to help [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/scrape-google-suggest/">Scrape Google Suggest at scale</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you work as an SEO consultant, one of your missions is to understand how the market evolves in order to adapt your strategy. Why? Because it is easier to get results if we get there first and we must always adapt to the changing behavior of our customers.</p>



<p>There are several tools to help us in this task, however we tend to overlook the most obvious one: Google Suggestions.</p>



<h2 class="wp-block-heading">What is Google Suggest?</h2>



<p>Everyone is using Google Suggest, most without even knowing it. When you start searching for something on Google, suggestions usually appear based on:</p>



<ul><li>The version of Google you use</li><li>Your language</li><li>Your search history</li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="958" height="574" src="https://www.dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_ejemplo.png" alt="" class="wp-image-454" srcset="https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_ejemplo.png 958w, https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_ejemplo-300x180.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_ejemplo-768x460.png 768w" sizes="(max-width: 958px) 100vw, 958px" /><figcaption>But it also takes into account current trends. I write this article hours after the announcement of the potential creation of the super league. And look what appears if I indicate &#8220;uefa&#8221;:</figcaption></figure>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="936" height="496" src="https://www.dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_superliga.png" alt="" class="wp-image-455" srcset="https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_superliga.png 936w, https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_superliga-300x159.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/05/google_suggest_superliga-768x407.png 768w" sizes="(max-width: 936px) 100vw, 936px" /><figcaption><br></figcaption></figure>



<p>Although no tool will indicate an interesting search volume for this search, I know it will have plenty over the next few days. So does Google, which is why it suggests it to me.<br>It is interesting, but extracting data from this tool manually can be tedious. It&#8217;s true, however there is an API that allows us to speed up the process a bit. The best part? You can scrape without complex configuration.</p>



<h2 class="wp-block-heading">TL;DR</h2>



<p>If you are not interested in knowing how to perform this extraction and you are only interested in the result, you can access directly to <a href="https://t.co/wDk2aNTONI?amp=1">the Google Colab I created</a> and follow the instructions. </p>



<p><br>If you are interested in the details of the code (made in Python), you can continue reading.<br></p>



<h2 class="wp-block-heading">Explanation of the code</h2>



<p><br>The code is quite simple, but I am going to explain each of the steps so that you can understand it and adapt it if you wish.</p>



<h3 class="wp-block-heading"><br>Loading libraries</h3>



<p><br>First, we have to load the different libraries. Some of them are quite well known (Pandas, Requests, JSON and BeautifulSoup) and you can find tutorials on the internet without any problem. The only not so common functionality I use is the ascii_lowercase function of the string library, which allows me to easily create a list with the different letters of the alphabet.</p>



<p><br>Why do I need it? You will understand this in one of the following sections. </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#load libraries
import pandas as pd 
import requests
from bs4 import BeautifulSoup
from string import ascii_lowercase
import json </code></pre></div>



<h3 class="wp-block-heading">Enter the seed keyword and country</h3>



<p><br>The Google Suggest API works only if you indicate a word, so that it returns suggestions. Therefore, you need to indicate one. In the Google Colab (which I shared with you at the beginning of the article), you will see that you can, as a user and without modifying the code, indicate the word you want.</p>



<p>This is possible thanks to the <strong>#@param {type: &#8220;string&#8221;}</strong> that I include at the end of each of the lines. It tells Google Colab that the user can modify the value of this variable. You can read more about this functionality <a href="https://www.tutorialspoint.com/google_colab/google_colab_adding_forms.htm">here</a>.</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#Get input from users
seed_kw = &quot;como taladrar&quot; #@param {type:&quot;string&quot;}
country = &quot;es&quot; #@param {type:&quot;string&quot;}</code></pre></div>



<h3 class="wp-block-heading">Extract suggestions</h3>



<p><br>This part allows you to extract suggestions using the Google Suggest API.</p>



<p>What does it do exactly?</p>



<ul><li>It creates a list of keywords, adding the letters of the alphabet after your initial word. If you enter shoes, you will get shoes a, shoes b etc…</li><li>Add to this same list, and following the same logic, your keyword followed by the numbers from 0 to 9.</li><li>Extract the keywords returned by the Google Suggest API for each of these combinations.</li></ul>



<p><br>Why do I follow this process? Simply because the Google Suggest API returns at most 10 suggestions. And if we add letters / number after your initial word, it allows to get more suggestions in a single run.</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#convert seed keyword to list
keywords = [seed_kw]
#create aditionnal seeed by appending a-z & 0-9 to it
for c in ascii_lowercase:
  keywords.append(seed_kw+&#39; &#39;+c)
for i in range(0,10):
  keywords.append(seed_kw+&#39; &#39;+str(i))

#gett all suggestions from Google
sugg_all = []
i=1

for kw in keywords:
  r = requests.get(&#39;http://suggestqueries.google.com/complete/search?output=toolbar&hl={}&q={}&#39;.format(country,kw))
  soup = BeautifulSoup(r.content, &#39;html.parser&#39;)
  sugg = [sugg[&#39;data&#39;] for sugg in soup.find_all(&#39;suggestion&#39;)]
  sugg_all.extend(sugg)</code></pre></div>



<h3 class="wp-block-heading">Extracting search volumes</h3>



<p><br>At the beginning of this article, I mentioned that search volumes are not always useful. It is true, but sometimes it can be interesting to have this information. In the end, you can also extract data from Google Suggest to develop the architecture of an E-commerce.</p>



<p>There are many APIs that allow you to extract this data, but for this tutorial I have decided to use the one proposed by Keyword Surfer:</p>



<ul><li>It is free</li><li>It does not require a password (create an account)</li><li>It does not have many limitations</li><li>It allows to extract volumes for up to 50 words in a single request</li></ul>



<p><br>Now, it&#8217;s not perfect, especially for words with a search volume lower than 50 monthly searches, but you can replace it with another API if you want. This part of the code extracts the search volume for each of the suggestions returned by the Google Suggest API, for the country you indicated in step 2.</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>#remove duplicated
sugg_all = pd.Series(sugg_all).drop_duplicates(keep=&#39;first&#39;)
if len(sugg_all)==0:
  print(&#39;There are no suggestion. The script can\&#39;t work :(&#39;)
  
#get search volume data 
#prepare keywords for encoding
data = sugg_all.str.replace(&#39; &#39;,&#39;%20&#39;).unique()
#divide kws into chunks of kws
chunks = [data[x:x+25] for x in range(0, len(data), 25)]

#create dataframe to receive data from API
results = pd.DataFrame(columns=[&#39;keyword&#39;,&#39;volume&#39;])

#get data 
for chunk in chunks:
  url = (
      &#39;https://db2.keywordsur.fr/keyword_surfer_keywords?country={}&keywords=[%22&#39;.format(country)+
      &#39;%22,%22&#39;.join(chunk)+
      &#39;%22]&#39;
  )

  r = requests.get(url)
  data = json.loads(r.text)

  for key in data.keys():
    results.loc[len(results)] = [key,data[key][&#39;search_volume&#39;]]</code></pre></div>



<h3 class="wp-block-heading">Save the results</h3>



<p><br>We are getting to the end! We only need to indicate when a word has no searches (by default, the Surfer API does not include words with a volume of 0) and save the result in a CSV that we can use later in Google Sheets or Excel.</p>



<p>Easy and fast!</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/scrape-google-suggest/">Scrape Google Suggest at scale</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/scrape-google-suggest/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>How newspapers leverage subdomain leasing?</title>
		<link>https://dev.aeripret.com/subdomain-leasing/</link>
					<comments>https://dev.aeripret.com/subdomain-leasing/#comments</comments>
		
		<dc:creator><![CDATA[aeripret]]></dc:creator>
		<pubDate>Sun, 28 Feb 2021 17:08:57 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.dev.aeripret.com/?p=128</guid>

					<description><![CDATA[<p>If you have been paying attention to the SERP landscape during the last years, you may have observed that a number of successful domains were operating under a subdomain or subfolder of another brand. This is true for a lot of industries but the one that has been impacted the most is the coupon industry. [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/subdomain-leasing/">How newspapers leverage subdomain leasing?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you have been paying attention to the SERP landscape during the last years, you may have observed that a number of successful domains were operating under a subdomain or subfolder of another brand.</p>



<p>This is true for a lot of industries but the one that has been impacted the most is the coupon industry. Here is a random query I ran, and out of the three first results, two actually belong to well-established news outlets.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="886" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/serp-cupon-adidas-1024x886.png" alt="" class="wp-image-129" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/serp-cupon-adidas-1024x886.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/serp-cupon-adidas-300x260.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/serp-cupon-adidas-768x664.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/serp-cupon-adidas.png 1341w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In this article, we are going to explain why we see such a situation in a lot of markets. You have exactly the same situation in France, Italy, Germany …</p>



<h2 class="wp-block-heading" id="newspapers-in-a-digital-first-world">Newspapers in a digital-first world</h2>



<p>Before tackling the main subject of this article, we must explain why we often see these subdomains or subfolders linked to newspapers and no other companies.</p>



<p>Some years ago, this industry was running on an advertising-only business model. With radio and TV, it was one of the most widespread ways of marketing a product or a brand. This is actually why we saw, in the early 2000s, the birth of free newspapers: the advertising money was enough to break-even: you didn’t have to charge for the content you were producing.</p>



<p>Today, the reality is very different: the market share of printed advertising has fallen dramatically. You can see the data for Spain below, extracted from&nbsp;<a href="https://www.slideshare.net/humanlevel/seo-y-visibilidad-del-contenido-de-calidad-en-modelos-de-suscripcin?ref=https://cards-frame.twitter.com/i/cards/tfw/v1/uc/1273214367685885959">an amazing presentation on SEO for newspapers</a>&nbsp;(in Spanish) that you should definitely check out if you are interested in this matter and can speak the language.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="861" height="520" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/advertising-spain.png" alt="" class="wp-image-130" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/advertising-spain.png 861w, https://dev.aeripret.com/wp-content/uploads/2021/02/advertising-spain-300x181.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/advertising-spain-768x464.png 768w" sizes="(max-width: 861px) 100vw, 861px" /></figure>



<p>And let me highlight what you already know:&nbsp;<strong>the internet advertising budget is not allocated anymore to newspapers</strong>: the vast majority is used on Google, Facebook, and Amazon. They still get a piece of the cake, but a (way) smaller part.</p>



<p>At the same time, newspapers had to create and maintain a digital presence, which obviously increased their costs. The equation was not working anymore and they had to find alternative ways of earning money. The most famous one was the creation of subscription plans, where you would pay a monthly or annual fee to access exclusive content, which came with its own challenges:</p>



<ul><li>To compensate for the loss of advertising revenue, you need a lot of premium subscribers, an objective that a lot of newspapers fail to achieve. The only success story I can think of is the New York Times (<a href="https://www.nytimes.com/2020/02/06/business/new-york-times-earning.html">see full story</a>)</li><li>A lot of internet users refuse to pay to consume content for two reasons: they don’t want to (they believe everything on the internet should be free) or because they sometimes don’t see the added value versus free content (which to be honest is sometimes true).</li></ul>



<p>Once newspapers realized that they weren’t going to break-even with only a subscription-based business model, they started to look for alternative solutions.&nbsp;<strong>One of them was to leverage their popularity to offer a subdomain or subfolder leasing to a third-party.</strong></p>



<p>You don’t know what the heck is a subdomain leasing? Don’t worry, let’s just go through that.</p>



<h2 class="wp-block-heading" id="what-is-a-subdomain-leasing">What is a subdomain leasing?</h2>



<p>When you own a domain, you can create a huge number of subdomains easily. A subdomain leasing is simply&nbsp;<strong>when one of these subdomains is leased to a third-party and is ran independently.</strong></p>



<p>Coupon sites are one example: a newspaper will create the subdomain and lease it to a company for a fee. In some cases, both parties are involved more in a partnership than a regular lease.</p>



<p>That is great, but what is even the point of paying for a subdomain if you can create your own domain? Let me explain why by showing an example in the Spanish market.</p>



<p>If you compare the visibility of two of the biggest names in this industry, you can see a normal trend for any given industry: you have fluctuations caused by changes applied to the project or external factors (a new competitor for instance), but nothing really surprising.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="378" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2-1024x378.png" alt="" class="wp-image-132" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2-1024x378.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2-300x111.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2-768x283.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2-1536x567.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-cupon-2.png 1883w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Now if I add two leased subdomains, you can clearly see that they achieved to keep up and then overcome these two websites in a matter of months when the industry is really competitive.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="381" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon-1024x381.png" alt="" class="wp-image-131" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon-1024x381.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon-300x112.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon-768x286.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon-1536x571.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/02/sistrix-sector-coupon.png 1880w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Let’s not explain just now how they can rank so fast, but you can understand why a company would be willing to pay just to use a subdomain of one of these newspapers.</p>



<h2 class="wp-block-heading" id="what-is-a-business-model-behind-a-cupon-website">What is a business model behind a cupon website?</h2>



<p>If you are not familiar with this industry, let me take a moment to explain how these websites earn a living. At the end of the day, if newspapers are interested in running o leasing a cupon subdomain (or subfolder), it is because they can make money.</p>



<p>There are several ways of making money, but the most common is through affiliation. When you redeem a coupon code as a user, two options:</p>



<ul><li>A cookie is added to your navigator to identify the cupon website as the affiliate when you make a purchase</li><li>You are redirected to the website where you want to purchase something and the coupon website is identified thanks to one URL parameters</li></ul>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="66" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/fnac-url-affiliate-1024x66.png" alt="" class="wp-image-133" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/fnac-url-affiliate-1024x66.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/fnac-url-affiliate-300x19.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/fnac-url-affiliate-768x50.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/fnac-url-affiliate.png 1098w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In both cases, the coupon website will get a commission out of the purchase you will make. What is funny is that&nbsp;<strong>the commission can sometimes be earned even if the coupon doesn’t work</strong>. Which explains why&nbsp;<a href="https://blog.zipfworks.com/promocodewatch-expired-coupon-report/">so many coupons are expired or not valid</a>.</p>



<p>Given the traffic you can achieve with these websites, it is very clear that you can make some real money in this industry <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="272" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/semrush-cuponation-1024x272.png" alt="" class="wp-image-134" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/semrush-cuponation-1024x272.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/semrush-cuponation-300x80.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/semrush-cuponation-768x204.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/semrush-cuponation.png 1184w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="how-do-leased-subdomains-or-folders-rank-so-fast">How do leased subdomains (or folders) rank so fast?</h2>



<p>Back in 2018,&nbsp;<a href="https://www.seroundtable.com/google-warns-about-leasing-subdomains-or-subfolders-28059.html">Google warned that leased subdomains could get hammered</a>. In reality,&nbsp;<strong>little happened because these subdomains are not worse than others</strong>. From a user perspective, they deliver the same thing: a coupon code. You can’t assimilate it to spamming, it is different.</p>



<p>So, how do these websites rank so fast? If we look at the number of links reported in Ahrefs for two major players in Spain,&nbsp;<strong>we actually see that cuponation.es has more and more domains</strong>.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="223" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-cuponation-1024x223.png" alt="" class="wp-image-135" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-cuponation-1024x223.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-cuponation-300x65.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-cuponation-768x167.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-cuponation.png 1271w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="227" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-descuento-elmund-1024x227.png" alt="" class="wp-image-136" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-descuento-elmund-1024x227.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-descuento-elmund-300x67.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-descuento-elmund-768x171.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/ahrefs-descuento-elmund.png 1220w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Yes, but the trick is not to look at how many but from where and how.&nbsp;<strong>I will use descuentos.abc.es to explain it.</strong></p>



<p>Like in a lot of countries, you have a lot of newspapers in Spain but they actually belong to a reduced number of media companies. If you want an example for the US, just&nbsp;<a href="https://detailed.com/google-control/">look at this amazing article written in 2016</a>.</p>



<p>So, the group behind ABC (Vocento) also owns several other brands, including one called&nbsp;<em>La Voz Digital</em>.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="195" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/footer-abc-1024x195.png" alt="" class="wp-image-137" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/footer-abc-1024x195.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer-abc-300x57.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer-abc-768x146.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer-abc.png 1394w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>This website has a link in its footer, using the keyword “<em>Descuentos</em>”, linking to… descuentos.abc.es.&nbsp;<strong>Not very surprising, I know.</strong></p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="165" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/footer.-la-voz-digital-1024x165.png" alt="" class="wp-image-138" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/footer.-la-voz-digital-1024x165.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer.-la-voz-digital-300x48.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer.-la-voz-digital-768x124.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/footer.-la-voz-digital.png 1530w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>On the main ABC website, you also have a link in the main navigation using the keyword “Cupones descuento”. Again, to descuentos.abc.es.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="616" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc-1024x616.png" alt="" class="wp-image-139" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc-1024x616.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc-300x180.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc-768x462.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc-1536x924.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/02/main-menu-abc.png 1548w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>You got the point, don’t you? These websites are basically leveraging their network to rank new domains/subdomains fast.&nbsp;<strong>And of course, Google will rank faster a website receiving a decent number of links from these popular websites than a new brand.</strong>&nbsp;Links do matter and I explained it&nbsp;<a href="https://www.dev.aeripret.com/case-study-rank-fast-using-politician-website">an older post</a>.</p>



<p>For newspapers, being able to rank quickly a website on a new vertical is great and it is completely normal that they leverage their capacity to do so. I mean, you have countless of examples of other industries&nbsp;<strong>where the German domain would link to the Italian domain using the exact keyword in Italian for instance.</strong>&nbsp;It is exactly the same. So why would we blame newspapers for doing that?</p>



<p>It also solves a part of the problem we mentioned at the beginning of this article. They can launch new businesses and diversify their income stream. But let’s be honest: they still to launch a few to compensate the shrink of their physical advertising revenue. They also need to be careful:&nbsp;<strong>you can’t ruin your brand reputation by leasing your subdomain to a bad third-party.</strong>&nbsp;That’s exactly why they actually don’t launch a dozen of new verticals at once, that would be quite risky.</p>



<p>For competitors, it means that&nbsp;<strong>you need to provide something else than just the cupon to compete with these websites</strong>. As you can’t compete with Amazon if you offer exactly the same, you need to differentiate yourself and work hard on your branding. Something that achieved the brand Chollometro in Spain for instance: they don’t just offer coupon, they also provide huge bargains. And everything is validated by the community to ensure it is real !</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="189" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro-1024x189.png" alt="" class="wp-image-140" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro-1024x189.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro-300x55.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro-768x142.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro-1536x284.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/02/homepage-chollometro.png 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="is-it-the-only-thing-happening-with-their-subdomains">Is it the only thing happening with their subdomains?</h2>



<p>Obviously, no. In terms of SEO, the news industry is very different,&nbsp;<strong>because you basically do a lot of real-time SEO</strong>. The vast majority of your content will generate traffic for less than 72H. Yes, you can create hub pages on key topics (coronavirus, world cup, etc…), but this doesn’t account for the majority of their traffic.</p>



<p>Hence, the news industry started to acquire (I don’t think these cases are leased but I may be wrong) existing projects and include them as subdomains, to rank on evergreen keywords as well.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="568" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/hostnames-elmundo-1024x568.png" alt="" class="wp-image-141" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/hostnames-elmundo-1024x568.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/hostnames-elmundo-300x166.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/hostnames-elmundo-768x426.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/hostnames-elmundo.png 1125w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Why? You don’t have to create everything from scratch and you can’t ensure that a&nbsp;<strong>part of your traffic comes from keywords with more stable search volume over time</strong>. Again, the idea is to diversify their stream of income because the old paradigm is broken and cannot be applied to the digital world.</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="387" src="https://www.dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix-1024x387.png" alt="" class="wp-image-142" srcset="https://dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix-1024x387.png 1024w, https://dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix-300x113.png 300w, https://dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix-768x290.png 768w, https://dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix-1536x581.png 1536w, https://dev.aeripret.com/wp-content/uploads/2021/02/mibebeyyo-sistrix.png 1883w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>You can also create new business models around these subdomains.&nbsp;<strong>If you have a recipe section, you can create a partnership with an online supermarket to allow your users to order the ingredients from them for instance.</strong></p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p>Subdomain leasing is here to stay, so we better get used to it. I don’t think it is a bad thing per se, because again these websites are fulfilling the search intent. They don’t rely only on the backlinks to rank, even if it helps a lot.</p>



<p><strong>Is it unfair?</strong>&nbsp;Big brands are always advantaged: Amazon would rank easily on a new commercial term than any other retailer for instance, and I don’t think this situation is any different. If you have a different opinion, I would happy to discuss it with you in the comment section though <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>The post <a rel="nofollow" href="https://dev.aeripret.com/subdomain-leasing/">How newspapers leverage subdomain leasing?</a> appeared first on <a rel="nofollow" href="https://dev.aeripret.com">Antoine Eripret</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dev.aeripret.com/subdomain-leasing/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
