Write Responsive Search Ads using Python 3 and Pandas
A quick and easy guide on how to automate your ad copy creation for immediate upload to Google Ads or Microsoft Ads Editor platforms.
Introduction
As a digital marketer, you may be faced with ad copy creation for your Google Ads or Microsoft Ads campaigns. With Responsive Search Ads becoming the preferred ad product of both Google and Microsoft, writing new copy that matches your old Expanded Text Ads sounds like a real time sink.
Luckily, it doesn’t have to be. You can put together your very own Python application that will write the copy for you (and still remain on-topic and on-brand). This tutorial will show you how to do this step-by-step, and ultimately allow you to output a .CSV file that is ready for upload into either Google Ads or Microsoft Ads directly. Feel free to use any code editor of your choice.
In this tutorial, we are going to be pretending to be an airline company focused on selling cheap flights.
Please note the variables provided in this file are simply for explanatory purposes and will require customization to work for your intended project. This program is written in a modular manner that requests user input for several variables and is purposefully designed to be shared with team members for collaboration on projects.
Let’s get started!
Import Modules and Libraries
This project is very straightforward and will only require two imports:
- The random module within Python (this will be used for copy randomization and for creating different permutations of our inputs)
- The pandas library (this will be used to create dataframes housing our copy outputs)
import random
import pandas as pd
Define Variables for Copy Randomization
The next step is to create a few variables which will hold strings necessary for our copy to randomize accordingly.
- adjectives = a list of strings that describes your product or service.
- headline_phrases = a list of strings that will be part of an ad’s Headline. Best practices indicate that these strings be written as call-to-actions.
- action_phrases = a list of strings that will be part of an ad’s Description Line. These are also call-to-actions, but not identical to the ones written in headline_phrases.
- semantic_variants = a list of strings that will be a part of an ad’s Description Line. These are variants that round out the Description Line and can even be written as taglines.
adjectives = ['Affordable ', 'Cheap ', 'Low-Cost ', 'Budget ']headline_phrases = ['Browse ', 'Book ', 'Find and Book ']action_phrases = ['Book Now and Save on ', 'Discover ', 'Find Deals on ', 'Save Big on ']semantic_variants = [" at Low Prices", " at Excellent Prices", " at Great Prices" ]
Request User Input for Topic
Your ad will need to be written about something, and this is the step where we will specify the topic. We will define a new variable called topic_phrases where a user can input exactly what the ad will be about.
topic_phrases = input("What is the campaign or ad group about?")
For best results, try listing at minimum 3 lexical permutations of what you are writing about for copy generation using an ampersand (&) as a delimiter.
For example: Flights&Hotels&Flight and Hotel Packages
If there are multiple items you would like to include in a single string, try pairing them using the following syntax:
For example: Flights, Hotels, and More&Flight, Hotels, and Car Rentals
Since we are potentially entering multiple items as part of the topic_phrases variable, we will need to split the strings into a list using the ampersand delimiter symbol.
topic_phrases = topic_phrases.split("&")
topic_phrases = list(topic_phrases)
The strings in this list will later be used for randomization for ad copy creation. More on that later.
Request User Input for Branding
Now that you’ve defined the topic of the ad, you may want the user to specify whether this is a branded ad or simply a generic one.
Using a while loop, we will request input for branding via the creation of a branded_request variable. If the ad is not to be a branded ad, we break the while loop. If the ad has been indicated to be a branded one, the while loop will continue until the specified brand has been inputted by the user.
while True:
branded_request = input("Is the campaign for a specific brand? Reply Y or N\n")
if branded_request == "Y":
branded_phrases = (input("Please specify the brand:")+ " ")
break
elif branded_request == " ":
print("You must specify a response. Please revise your input:\n")
continue
elif branded_request == "N":
branded_phrases = ""
break
Request User Input for One Additional Adjective
We’ve already defined a default set of adjectives for our product or service via the adjectives variable. Even though we may have exhausted our ability to come up with a descriptive string, there is likely an adjective we may have missed. Automated ad copy is only as good as the variety and quality of its inputs, and as such, we ought to get one more adjective from the user to increase our semantic variation.
Once again, we create a while loop in which the additional_adjective variable resides. The user must specify an additional adjective, else the while loop will not break.
Whatever additional adjective is provided is then appended to our original adjectives variable for copy randomization.
while True:
additional_adjective = (input("Please specify one adjective to describe the product or service:\n") + " ")
if additional_adjective == " ":
print("You must specify an adjective. Please revise your input:\n")
continue
else:
break
adjectives.append(additional_adjective)
print(adjectives)
Request User Input for Final Destination URL
The ad needs to lead somewhere upon being clicked — we specify our destination by creating another variable named final_url. The exact URL of the ad will need to be entered here and can simply be pasted from the user’s clipboard.
final_url = input("Please specify a final destination URL for this set of RSAs:")
Ad Headline Creation
All of our variables are set up now and we can begin creating our headlines. We begin by creating a variable in which we will house the results of our randomized permutations — in this instance, I’ve called my variable pd_headline just to denote that these headlines will eventually end up in a pandas dataframe.
pd_headline is an empty list currently, but the results of the randomized headline elements will be appended to this empty list.
pd_headline = []
Next, we define a function headline which utilizes naïve Pythonic for loops to generate individual headlines. A new variable, headline_set, will attempt to store a maximum of 15 total headlines. Note that it is unlikely that all 15 headlines will be created given the character restriction of 30 found in Google and Microsoft Ads.
Headlines are then generated using our previously imported random module, from which a random choice of items in headline_phrases, branded_phrases, adjectives, semantic_variants, and topic_phrases are selected. If the result is less than or equal to 30 characters, the result is added to the headline_set variable. This is repeated in three cycles:
- Cycle 1: Iterates 5 times for a headline creation including a random choice of items found in headline_phrases, branded_phrases, and topic_phrases
- Cycle 2: Iterates 5 times for a headline creation including a random choice of items found in adjectives and topic_phrases
- Cycle 3: Iterates 5 times for a headline creation including a random choice of items found in topic_phrases and semantic_variants
def headline():
count = 0
headline_set = set()
for count in range (1,6):
headline = (random.choice(headline_phrases) + branded_phrases + random.choice(topic_phrases))
if len(headline) <= 30:
headline_set.add(headline)
count += 1
for count in range (6,11):
headline = (random.choice(adjectives) + random.choice(topic_phrases))
if len(headline) <= 30:
headline_set.add(headline)
count += 1
for count in range (11,16):
headline = (random.choice(topic_phrases) + random.choice(semantic_variants))
if len(headline) <= 30:
headline_set.add(headline)
count += 1
headline_set now stores all iterations of headlines that do not violate our character count limit of 30. Each item in the headline_set variable is appended to our empty list that we created at the start of the headline function, pd_headline.
for headline in headline_set:
print(headline)
pd_headline.append(headline)
headline()
Ad Description Line Creation
Headlines are out of the way, now we want to generate Description Lines. We repeat the process designated in the headlines function, but this time, we only run the process for one cycle as we are allowed only 4 Description Lines in a Responsive Search Ad. We will go ahead and create 5 iterations of Description Lines, just in case one or two of them violate the character count limit of 90.
pd_description = []
def description():
count = 0
for count in range (0,5):
description = (random.choice(action_phrases) + random.choice(adjectives) + branded_phrases + random.choice(topic_phrases) + random.choice(semantic_variants) + ".")
if len(description) < 90:
print(description)
pd_description.append(description)
description()
Fill Dataframe with Inputs
Our Headlines are now stored as lists in pd_headline and our Description Lines are now stored as lists in pd_description. All that remains is organizing our results in a dataframe for immediate export to a CSV file format.
We create three separate dataframes to ensure our results are organized properly.
The first and second dataframes will store our Headlines and Description Lines respectively, with enumeration for each column in each dataframe to satisfy the column syntax required in Google Ads and Microsoft Ads editors.
df1 = pd.DataFrame()
for headline in pd_headline:
df1.insert(0, "Headline", [headline], allow_duplicates = True)
df1.columns = [f'{x} {i}' for i, x in enumerate(df1.columns, 1)]df2 = pd.DataFrame()
for description in pd_description:
df2.insert(0, "Description", [description], allow_duplicates = True)
df2.columns = [f'{x} {i}' for i, x in enumerate(df2.columns, 1)]
The final dataframe will store the value for the final URL we specified earlier. This dataframe is created separately from the others to avoid continued enumeration across its column heading values.
df3 = pd.DataFrame()
df3.insert(0, "Final URL", [final_url], allow_duplicates = True)
Export to CSV
Lastly, we join together our three dataframes and export the result to CSV. I’ve gone ahead and named my export ‘Automated_Responsive_Search_Ads.csv’, but you can name it whatever you like.
df_export = pd.concat([df1, df2, df3], axis=1, join="inner")
df_export.to_csv('Automated_Responsive_Search_Ads.csv', index=False)
Please note that the final output file will be found in the same directory as where you have created your program. In addition, please note that each iteration of running this program will cause the old output file to be overwritten.
Example of Output
Let’s review our program and execute it as an example. As mentioned before, we are going to be pretending to be an airline company focused on selling cheap flights.
What is the campaign or ad group about?
Flights
Is the campaign for a specific brand? Reply Y or N
Y
Please specify the brand: Air Canada
Please specify one adjective to describe the product or service:
Economy
['Affordable ', 'Cheap ', 'Low-Cost ', 'Budget ', 'Economy ']
Please specify a final destination URL for this set of RSAs:
www.mycooltravelsite.com
We can now view the dataframes filled with our generated ad copy. The Headlines are displayed first, followed by the Description Lines.
Budget Flights
Book Air Canada Flights
Low-Cost Flights
Flights at Excellent Prices
Economy Flights
Browse Air Canada Flights
Flights at Great Prices
Flights at Low Prices
Save Big on Economy Air Canada Flights at Low Prices.
Discover Affordable Air Canada Flights at Great Prices.
Find Deals on Budget Air Canada Flights at Low Prices.
Book Now and Save on Affordable Air Canada Flights at Excellent Prices.
Discover Affordable Air Canada Flights at Excellent Prices.
Lastly, we verify the file’s creation in our main program directory to ensure the CSV is correctly displayed.
If you are having trouble with replicating the code, you can feel free to download the open source version of this tutorial in both .py and Jupyter Notebook format at https://github.com/rfinatan/Open-Source-Responsive-Search-Ads-Generator-1.1.