Migrating from Bitly to YOURLS

    A little while ago, I needed to move a client off Bitly to YOURLS. YOURLS is a self-hosted URL shortening service similar to Bitly, but with much more flexibility. The main reasons we moved across to YOURLS was for:

    • The ability to have 1 character long keywords for short url. e.g. https://t.co/1
    • Full customisation of application
    • Great range of plugins
    • Decent out-of-the-box statistics
    • No API limits (we were doing a lot of querying against Bitly and hitting API limits).

    The biggest hurdle to jump when moving to our YOURLS installation was the migration of existing short urls that had been created inside Bitly. We considered at one point, just leaving everything older than 3 months behind and starting fresh. However the client had a lot referral traffic from sites like Pinterest and Tumblr which was a couple of years old in some cases.

    Below is a quick and simple PHP script that loops through your Bitly account and scrapes the link history and then imports it into your YOURLS application.

    It doesn’t pull the link history in as YOURLS (at the time of writing) doesn’t have a native stats endpoint to write statistics to; and understandably so, there are not many use cases that would warrant writing volumes of link stats to your installation.

    Importing your Bitly links into YOURLS

    <?php 
    /*
    * Code by Dom Sammut
    * Cannot be redistributed as part of any paid package or product. Open Source only :)
    */
    
    define( 'YOURLS_HOST', 'domsammut.com' ); 
    define( 'BITLY_TOKEN', '###' ); 
    define( 'YOURLS_SIGNATURE', '##' );
     
    $bitly_result = TRUE;
    $origin       = 'https://api-ssl.bitly.com/v3/user/link_history?format=json&access_token=' . BITLY_TOKEN . '&limit=50'; 
    $offset       = 0; 
    
    while ( $bitly_result === TRUE ) {
    
            // Init the CURL request 
            $ch = curl_init(); 
    
            // Setup the bitly api url including offset variable 
            curl_setopt( $ch, CURLOPT_URL, $origin . '&offset=' . (string) $offset ); 
    
            // Don't return headers in output 
            curl_setopt( $ch, CURLOPT_HEADER, 0 ); 
    
            // Return response of CURL as string 
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); 
    
            // Execute the request and reutrn to $response variable 
            $response = curl_exec( $ch );
     
            // Close the request 
            curl_close( $ch );
     
            // Decode JSON response 
            $response = json_decode( $response, TRUE ); 
    
            // Check to see if there are anymore bitly links. 
            // If not, set the $bitly_result to FALSE so we 
            // can exit the while loop. Else we increment 
             // the $offset by 50. 
    
            if ( $response['data']['link_history'] > 50 ) { 
    	        $bitly_result = FALSE; 
            } else {
    	        $offset += 50; 
            }
    	
    	// Let's now loop through our $response data from Bitly
    	// and push each link into YOURLS via the API.
    	foreach ( $response['data']['link_history'] as $link ) { 
    
    		// Get the short link and strip everything except
    		// for the actual subdirectory path. E.G
    		// bit.ly/Ye0sez=> Ye0sez
    		$shortlink = preg_split( "/^.*\//", $link['link'] ); 
    
    		// Get the title as provided by Bitly
    		$title = $link['title']; 
    
    		// Get the full URL that the shortlink redirects to
    		$long_url = $link['long_url']; 
    
    
    
    		// Init the CURL session 
    		$ch = curl_init(); 
    
    		// Set the YOURLS endpoint
    		curl_setopt( $ch, CURLOPT_URL, "http://" . YOURLS_HOST . '/yourls-api.php' ); 
    		
    		// No header in the result 
    		curl_setopt( $ch, CURLOPT_HEADER, 0 ); 
    		
    		// Return, do not echo result 
    		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, TRUE ); 
    
    		// This is a POST request 
    		curl_setopt( $ch, CURLOPT_POST, 1 ); 
    
    		// Data to POST 'format' => 'value',
    		curl_setopt( $ch, CURLOPT_POSTFIELDS, array( 
    			'url'       => $long_url,
    			'keyword'   => $shortlink[1],
    			'title'     => $title,
    			'action'    => 'shorturl',
    			'signature' => YOURLS_SIGNATURE
    		) );
    
    		// Fetch and return content
    		$yourlsResponse = curl_exec( $ch );
    
    		// Close CURL request
    		curl_close( $ch );
    
    		// You can do something with $yourlsResponse
    
    	}
    
    }
    
    

    Be sure to take a look at the Bitly Rate Limiting page. You potentially might need to batch this script and have it pull 1000 URLs per hour for example, in which case you’d just need to add a counter to your while loop or use the $offset variable.

    Importing Stats

    I haven’t shared the script for this component as it exposes private client information. However, we scraped basic click statistics from Bitly and then wrote them directly into the YOURLS database.

    This component required multiple API calls for each link and was only completed for the top 500 URLS due to the time and value of the information. For the remaining URLs we simply pulled the total clicks.

    We executed this component AFTER we had migrated our domain to the YOURLS installation to ensure we didn’t miss any data. We did it roughly 7 days after, to ensure we had ironed out any issues with the installation. When writing the statistical data, we combined it with any new data that had been collected.

    Hopefully this helps you out a little!

    This post was last modified on August 19, 2017 9:06 pm

    Dom Sammut: Dom Sammut is a PHP / Node.js Web Developer from Australia with extensive experience in developing in and customising Laravel, Express, VueJS, WordPress, Symphony CMS, Craft CMS and Squiz Matrix.

    View Comments (1)

    • This is exactly the sort of thing I was looking for since Bitly has now changed their services, and their API is changing, and I really need to move to my own YOURLS instance to have more control. Is there any way to get a cleaned up version of the stats script as I would love to have something as a starting point, even if I need to fill in gaps. Thanks so much for sharing this!

    Related Post