Select to view content in your preferred language

Backup notebook script to backup by categories assigned in ArcGIS Online. Partially works.

198
4
2 weeks ago
AndrewHankinson
Regular Contributor

I have a script that seems to work for taking back-ups based on categories assigned in AGOL. I run the script via a notebook in ArcGIS Pro and this works for some of the layers, for other layers I get a return of 

INFO:root:2025-09-24 15:06:54 - Backup process started.
INFO:root:2025-09-24 15:07:05 - Searching for items in category: 'Category name'
INFO:root:2025-09-24 15:07:05 - No items found in the category 'Category name'.
INFO:root:2025-09-24 15:07:05 - Script completed successfully.

The obvious thing to me would be to check the categories, I have and the layers have been categorised and I appear to be using the exact category in the script query. I have removed categories and then re-assigned and I get the same issue. 

I have ran another script to check which layers have categories assigned and then provides a summary count for each category. Which itself doesn't seem to count everything correctly, but I'll take that as a sidebar for now and ignore that.  

What is strange is this worked for a number of categories I initially tested it with and then just returns nothing for others despite them being categorised. 

Any suggestions on where this script falls down would be greatly appreciated. 

from time import strftime  
from arcgis.gis import GIS  
import logging  
import os  

# Initialize logging  
log_file = r"C:\ArcGIS_Pro_backups\backup_log.txt" 
logging.basicConfig(filename=log_file, level=logging.INFO)

def log_message(message):  
    logging.info(f"{strftime('%Y-%m-%d %H:%M:%S')} - {message}")  

# Input Variables  
base_output_dir = r"C:\ArcGIS_Pro_backups"  
date_str = strftime('%Y_%m_%d')  
categories_to_backup = ["Category 1"]  # <-- Add your categories here

# Create Backup Function  
def backup_feature_services():  
    try:  
        # Log into AGOL  
        gis = GIS('https://arcgis.com', 'Username', 'Password')

        # Create a folder for today's date  
        date_folder_path = os.path.join(base_output_dir, date_str)  
        if not os.path.exists(date_folder_path):  
            os.makedirs(date_folder_path)  

        # Loop through each category  
        for category in categories_to_backup:  
            log_message(f"Searching for items in category: {category}")  
            search_query = f"categories:{category}"  
            items = gis.content.search(query=search_query, item_type='Feature Service')  

            if not items:  
                log_message(f"No items found in the category '{category}'.")
                continue  

            # Create a subfolder for this category  
            category_folder_path = os.path.join(date_folder_path, category.replace(" ", "_"))
            if not os.path.exists(category_folder_path):  
                os.makedirs(category_folder_path)  

            for item in items:  
                try:  
                    log_message(f"Processing item: {item.title} (ID: {item.id}) in category '{category}'")  

                    # Export the item to File Geodatabase  
                    tempfile = f"{item.title}_{date_str}"  
                    export_item = item.export(tempfile, 'File Geodatabase', parameters=None, wait=True)  

                    # Search and download the exported File Geodatabase  
                    myexport = gis.content.search(tempfile, item_type='File Geodatabase')  
                    if myexport:  
                        fgdb = gis.content.get(myexport[0].id)  
                        fgdb.download(save_path=category_folder_path)  
                        fgdb.delete()  
                        log_message(f"Successfully backed up: {item.title} to {category_folder_path}")  
                    else:  
                        log_message(f"Error: Export for {item.title} not found.")  
                  
                except Exception as e:  
                    log_message(f"Error processing item {item.title}: {str(e)}")  

        log_message("Script completed successfully.")  

    except Exception as e:  
        log_message(f"General error: {str(e)}")  

# Execute the script  
if __name__ == "__main__":  
    if not os.path.exists(base_output_dir):  
        os.makedirs(base_output_dir)  
    log_message("Backup process started.")  
    backup_feature_services()

 

0 Kudos
4 Replies
PeterKnoop
MVP Regular Contributor

It looks like you have a space in the name of the category for which you are searching, 'Category name'. You likely need to enclose it in quotes when creating your query to ensure it behaves as you expect.

0 Kudos
AndrewHankinson
Regular Contributor

@PeterKnoop thanks for getting back to me on this, I did try enclosing the 'Category name' with quotes but then I got an error with the folder name not liking the quotation marks. 
I'm not sure if it is an issue with how the script is querying categories, I had a look at the ArcGIS REST Services Directory and under info > iteminfo I see

"categorites" : [
    "/Categories/NCPGS 2025-26"

Having said this I did change the query to be '/Categories/Category name' and got nothing back and it doesn't really explain why it would work for some categories and not others. 

In the the end I reverted to using tags as the original script did and just updated the tags on AGOL using the ArcGIS Admin tools. 

0 Kudos
PeterKnoop
MVP Regular Contributor

@AndrewHankinson using gis.content.search with the query parameter to search for categories can be a challenge, as it is a fuzzy search. If you have two categories set up, "Category 1" and "Category 2", then you need to be explicit with quotes. For example,

items = gis.content.search(query = 'categories:"Category Test"')

will not return the same results as:

items = gis.content.search(query='categories:Category Test')

The first looks for any items with a category that contains "Category Test", while the latter's query terms are interpreted as "categories:Category" AND "Test", so it will return any items that contain "Category" in their category AND contain "Test" in their name, description, summary, etc.

Sometimes the fuzziness of querying for categories can be useful. For example, maybe you want to search for any category that contains "Test", with a query value of 'categories:Test'.

If you are interested in searching explicitly for Category names, however, then I would recommend using the categories parameter instead. For example:

items = gis.content.search(query = '', categories="/Categories/Category 1")

will return only items that have "/Categories/Category 1" as a category.

 

AndrewHankinson
Regular Contributor

@PeterKnoop thanks for the detailed response, I have used 

categories_to_backup = ["/Categories/Category 1", "/Categories/Category 2"]


and this seems to work, thank you.  

0 Kudos