Google+ Peter Bromberg's .NET Blog | Build an Indeed Job Search Engine

Peter Bromberg's .NET Blog All Things Programming

Build an Indeed Job Search Engine

5. September 2016 15:59 by admin in API, ASP.NET

A common task for developers is  - you guessed it – to search for a job! This post will show you how to build a standard ASP.NET App that uses the Indeed API to make searching for jobs easy.  I’ll show the basics of how I constructed the app and included will be a download with the complete Visual Studio 2015 source code. The only thing you’ll need is a new Indeed publisher API key to insert into the AppSettings section of your web.config, and you should start out by getting that here:

http://www.indeed.com/publisher

The Indeed API is a very simple, REST – based API that returns XML. So we’ll have two classes for our search interface – the result class, and the QueryParameters class, which holds the various query parameters that can be passed on the querystring of the API URL.

Here’s the QueryParameters class first:

public class IndeedQueryParameters

    {

        /// <summary>

        /// Searches Job Title.

        /// </summary>

        public string JobQuery { get; set; }

       

        /// <summary>

        /// Searches for jobs near a particular location.

        /// </summary>

        public string Location { get; set; }

        /// <summary>

        /// Sort by relevance or date. Default is relevance.

        /// </summary>

        public string Sort { get; set; }

        /// <summary>

        /// Distance from search location ("as the crow flies"). Default is 25.

        /// </summary>

        public string SearchRadius { get; set; }

        /// <summary>

        /// Site type. To show only jobs from job boards use 'jobsite'. For jobs from direct employer websites use 'employer'.

        /// </summary>

        public string St { get; set; }

        /// <summary>

        /// Job type. Allowed values: "fulltime", "parttime", "contract", "internship", "temporary".

        /// </summary>

        public string Jt { get; set; }

        /// <summary>

        /// Start results at this result number, beginning with 0. Default is 0.

        /// </summary>

        public string Start { get; set; }

        /// <summary>

        /// Maximum number of results returned per query. Default is 10

        /// </summary>

        public string Limit { get; set; }

        /// <summary>

        /// Number of days back to search.

        /// </summary>

        public string FromAge { get; set; }

        /// <summary>

        /// Filter duplicate results. 0 turns off duplicate job filtering. Default is 1.

        /// </summary>

        public string Filter { get; set; }

        /// <summary>

        /// If latlong=1, returns latitude and longitude information for each job result. Default is 0.

        /// </summary>

        public string LatitudeLongitude { get; set; }

        /// <summary>

        /// Search within country specified. Default is us. See below for a complete list of supported countries.

        /// </summary>

        public string Country { get; set; }

        /// <summary>

        /// Channel Name: Group API requests to a specific channel.

        /// </summary>

        public string Channel { get; set; }

        /// <summary>

        /// The IP number of the end-user to whom the job results will be displayed. This field is required.

        /// </summary>

        public string UserIP { get; set; }

        /// <summary>

        /// The User-Agent (browser) of the end-user to

        /// whom the job results will be displayed.

        /// This can be obtained from the "User-Agent"

        /// HTTP request header from the end-user.

        /// This field is required.

        /// </summary>

        public string UserAgent { get; set; }

    }

And the IndeedSearchResult class which maps to the returned XML document:

public class IndeedSearchResult
{
     /// <summary>
     /// The time the job listing was posted formatted
     /// for display.
     /// </summary>
     /// <example>6 days ago</example>
     public string FormattedRelativeTime { get; set; }

     /// <summary>
     /// Has this job listing expired?
     /// </summary>
     /// <example>false</example>
     public string Expired { get; set; }

     /// <summary>
     /// Is this an Indeed sponsored job?
     /// </summary>
     /// <example>false</example>
     public string Sponsored { get; set; }

     /// <summary>
     /// The indeed job key.
     /// </summary>
     /// <example>0123456789abcdef</example>
     public string JobKey { get; set; }

     /// <summary>
     /// Latitude of the job's location.
     /// </summary>
     /// <example>41.057693</example>
     public string Latitude { get; set; }

     /// <summary>
     /// Longitude of the job's location.
     /// </summary>
     /// <example>-73.54395</example>
     public string Longitude { get; set; }

     /// <summary>
     /// Code to be placed on the onMouseDown event of the title link.
     /// </summary>
     /// <example>indeed_clk(this, '');</example>
     public string OnMouseDown { get; set; }

     /// <summary>
     /// A short description of the summary of the job.
     /// </summary>
     /// <example>Indeed is looking for strategic Account Executives to help in the expansion of our Stamford, CT location. We are
     /// in the process of interviewing sales candidates who have 2-5 years of sales experience and who have experience
     /// generating new business and growing existing accounts. A strong candidate will have excellent communication
     /// skills, consistent work ethic and a desire to be a part of the...</example>
     public string Snippet { get; set; }

     /// <summary>
     /// The date that the job was posted.
     /// </summary>
     /// <example>Tue, 03 Aug 2010 14:00:47 GMT</example>
     public string Date { get; set; }

     /// <summary>
     /// The country where the job is located.  This
     /// will be in the format of a country code.
     /// </summary>
     /// <example>US</example>
     public string Country { get; set; }

     /// <summary>
     /// The state where the job is located.
     /// </summary>
     /// <example>CT</example>
     public string State { get; set; }

     /// <summary>
     /// The source of the job posting.
     /// </summary>
     /// <example>Indeed</example>
     public string Source { get; set; }

     /// <summary>
     /// The city where the job is located.
     /// </summary>
     /// <example>Stamford</example>
     public string City { get; set; }

     /// <summary>
     /// The job title.
     /// </summary>
     /// <example>Sales Account Executive</example>
     public string JobTitle { get; set; }

     /// <summary>
     /// The company the job is for.
     /// </summary>
     /// <example>Indeed</example>
     public string Company { get; set; }

     /// <summary>
     /// Full location of the job listing that is
     /// formatted for display.
     /// </summary>
     /// <example>Stamford, CT 06902</example>
     public string FormattedLocationFull { get; set; }

     /// <summary>
     /// The URL of the job posting on Indeed.com
     /// </summary>
     /// <example>http://www.indeed.com/rc/clk?jk=0123456789abcdef</example>
     /// <remarks>Notice that the job key is part of the URL.</remarks>
     public string URL { get; set; }
}

With these items complete, all we need is our search method so that we can bind results to a standard ASP.NET DataGrid:

public static List<IndeedSearchResult> GetSearchResults(IndeedQueryParameters parameters, string apiPublisherKey)
      {
          Contract.Requires(null != apiPublisherKey);
          Contract.Requires("" != apiPublisherKey.Trim());
          // To get your own Publisher ID:  http://www.indeed.com/publisher

          string requestUrl = "http://api.indeed.com/ads/apisearch" +
                                          String.Format("?publisher={0}", apiPublisherKey) +
                                          String.Format("&q={0}", parameters.JobQuery) +
                                          String.Format("&l={0}", parameters.Location) +
                                          String.Format("&sort={0}", parameters.Sort) +
                                          String.Format("&radius={0}", parameters.SearchRadius) +
                                          String.Format("&st={0}", parameters.St) +
                                          String.Format("&jt={0}", parameters.Jt) +
                                          String.Format("&start={0}", parameters.Start) +
                                          String.Format("&limit={0}", parameters.Limit) +
                                          String.Format("&fromage={0}", parameters.FromAge) +
                                          String.Format("&filter={0}", parameters.Filter) +
                                          String.Format("&latlong={0}", parameters.LatitudeLongitude) +
                                          String.Format("&co={0}", parameters.Country) +
                                          String.Format("&chnl={0}", parameters.Channel) +
                                          String.Format("&userip={0}", parameters.UserIP) +
                                          String.Format("&useragent={0}", parameters.UserAgent) +
                                          "&v=2";

          XmlDocument doc = new XmlDocument();
          doc.Load(requestUrl);
          XmlElement root = doc.DocumentElement;
          XmlNodeList nodes = root.SelectNodes("//results//result");

          List<IndeedSearchResult> results = new List<IndeedSearchResult>();
          foreach (XmlNode node in nodes)
          {
              IndeedSearchResult result = new IndeedSearchResult();
              result.JobTitle = node["jobtitle"].InnerText;
              result.Company = node["company"].InnerText;
              result.City = node["city"].InnerText;
              result.State = node["state"].InnerText;
              result.Country = node["country"].InnerText;
              result.FormattedLocationFull = node["formattedLocation"].InnerText;
              result.Source = node["source"].InnerText;
              result.Date =DateTime.Parse( node["date"].InnerText).ToShortDateString();
              result.Snippet = node["snippet"].InnerText;
              result.URL = node["url"].InnerText;
              result.OnMouseDown = node["onmousedown"].InnerText;
            
                
                 if ( node["latitude"] !=null)  // prevent blow-ups if null
              result.Latitude = node["latitude"].InnerText;
              result.JobKey = node["jobkey"].InnerText;
              result.Sponsored = node["sponsored"].InnerText;
              result.Expired = node["expired"].InnerText;
              result.FormattedLocationFull = node["formattedLocationFull"].InnerText;
              result.FormattedRelativeTime = node["formattedRelativeTime"].InnerText;

              results.Add(result);
          }
          results = results.OrderByDescending(x => x.Date).ToList();
          return results;
      }

The final step is to perform a search based on the minimum number of QueryParameter inputs, namely the job search term e.g. “.NET Developer” and the location, e.g. “Orlando FL” and we’re done:

protected void DoSearch(int pagestart = 0)
       {
           Indeed.IndeedSearch srch = new IndeedSearch();
           IndeedQueryParameters parms = new IndeedQueryParameters();
           parms.Start = pagestart.ToString();
           parms.JobQuery = txtSearch.Text;
           parms.Location = txtLocation.Text;
           parms.Limit = 1000.ToString();
           parms.UserIP = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[1].ToStrin‌‚Äčg();


           List<IndeedSearchResult> results = srch.GetSearchResults(parms);
           GridView1.PageIndex = pagestart;
           GridView1.DataSource = results;
           GridView1.DataBind();

       }

 

I’ve added paging and sorting by job date.  You can download the complete source code here:

IndeedWeb.zip (10.95 mb)

Add comment

  Country flag


Loading