├── src ├── test │ └── java │ │ ├── serpapi │ │ ├── data │ │ │ ├── error_sample.json │ │ │ ├── account.json │ │ │ ├── location.json │ │ │ ├── search_coffee_sample.html │ │ │ └── search_coffee_sample.json │ │ ├── ParameterStringBuilderTest.java │ │ ├── ReadJsonFile.java │ │ ├── SearchArchiveApiTest.java │ │ ├── BingSearchTest.java │ │ ├── BaiduSearchTest.java │ │ ├── YahooSearchTest.java │ │ ├── GoogleSearchTest.java │ │ ├── YandexSearchTest.java │ │ ├── EbaySearchTest.java │ │ ├── LocationApiTest.java │ │ ├── AccountApiTest.java │ │ ├── GoogleSearchImplementationTest.java │ │ ├── GoogleSearchClientTest.java │ │ ├── SerpApiClientTest.java │ │ └── GoogleSearchFullTest.java │ │ └── basic_search_result.json └── main │ └── java │ └── serpapi │ ├── SerpApiSearchException.java │ ├── BingSearch.java │ ├── GoogleSearch.java │ ├── ParameterStringBuilder.java │ ├── EbaySearch.java │ ├── YahooSearch.java │ ├── BaiduSearch.java │ ├── YandexSearch.java │ ├── SerpApiSearch.java │ └── SerpApiHttpClient.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── demo ├── .gitignore ├── settings.gradle ├── Makefile ├── build.gradle └── src │ └── main │ └── java │ └── demo │ └── App.java ├── .gitignore ├── settings.gradle ├── Makefile ├── .github └── workflows │ └── gradle.yml ├── LICENSE ├── gradlew └── README.md /src/test/java/serpapi/data/error_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "Your account credit is too low" 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serpapi/google-search-results-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | gradle/ 4 | gradlew 5 | 6 | # Ignore Gradle build output directory 7 | build 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .idea/ 3 | .gradle/ 4 | .settings/ 5 | .classpath 6 | .project 7 | bin/ 8 | data.json 9 | demo/gradle/ 10 | demo/libs/*.jar 11 | derive.rb 12 | gradlew.bat 13 | switch_java.sh 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/test/java/serpapi/data/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_id": "5ac54d6adefb2f1dba1663f5", 3 | "api_key": "demo", 4 | "account_email": "demo@serpapi.com", 5 | "plan_id": "bigdata", 6 | "plan_name": "Big Data Plan", 7 | "searches_per_month": 30000, 8 | "this_month_usage": 24042, 9 | "last_hour_searches": 42 10 | } 11 | -------------------------------------------------------------------------------- /demo/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user guide at https://docs.gradle.org/5.0/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'demo' 11 | -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | ifndef API_KEY 4 | $(error "API_KEY must be defined") 5 | else 6 | $(info "found API_KEY variable") 7 | endif 8 | 9 | all: init clean dep build run 10 | 11 | init: 12 | gradle wrapper 13 | chmod +x ./gradlew 14 | 15 | clean: 16 | ./gradlew clean 17 | 18 | dep: 19 | $(MAKE) -C ../ build 20 | rm -rf libs/*.jar 21 | cp ../build/libs/* libs 22 | 23 | build: clean 24 | @./gradlew build 25 | 26 | run: 27 | @./gradlew run --args="$(API_KEY)" 28 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * In a single project build this file can be empty or even removed. 6 | * 7 | * Detailed information about configuring a multi-project build in Gradle can be found 8 | * in the user guide at https://docs.gradle.org/4.2/userguide/multi_project_builds.html 9 | */ 10 | 11 | rootProject.name = 'google_search_results_java' 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | all: clean test 4 | 5 | # clean 6 | clean: 7 | ./gradlew clean 8 | 9 | # Run gradle test with information 10 | test: 11 | ./gradlew test --info 12 | 13 | build: clean 14 | # ./gradlew build -x test 15 | ./gradlew build publishToMavenLocal 16 | @echo "see build/lib" 17 | 18 | oobt: build 19 | $(MAKE) -C demo all 20 | 21 | doc: 22 | gradle javadoc:javadoc 23 | 24 | # Create a release using GitHub 25 | release: doc build 26 | @echo "drag drop file" 27 | open build/libs/ 28 | open build/distributions/ 29 | open -a "Google Chrome" https://github.com/serpapi/google-search-results-java/releases 30 | -------------------------------------------------------------------------------- /src/test/java/serpapi/ParameterStringBuilderTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import org.junit.Test; 4 | import serpapi.ParameterStringBuilder; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /*** 12 | * Test converting map to URL query 13 | */ 14 | public class ParameterStringBuilderTest 15 | { 16 | @Test 17 | public void getParamsString() throws Exception 18 | { 19 | Map map = new HashMap(); 20 | map.put("output", "json"); 21 | map.put("key", "value"); 22 | 23 | assertEquals(ParameterStringBuilder.getParamsString(map), 24 | "output=json&key=value"); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/SerpApiSearchException.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | /** 4 | * SerpApi Search Exception wraps any exception related to the connection with SerpApi.com 5 | */ 6 | public class SerpApiSearchException extends Exception { 7 | /** 8 | * Constructor 9 | */ 10 | public SerpApiSearchException() { 11 | super(); 12 | } 13 | 14 | /** 15 | * Constructor 16 | * @param exception original exception 17 | */ 18 | public SerpApiSearchException(Exception exception) { 19 | super(exception); 20 | } 21 | 22 | /** 23 | * Constructor 24 | * @param message short description of the root cause 25 | */ 26 | public SerpApiSearchException(String message) { 27 | super(message); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: test 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: test 26 | run: make test 27 | env: 28 | API_KEY: ${{secrets.API_KEY}} -------------------------------------------------------------------------------- /src/test/java/serpapi/ReadJsonFile.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonParser; 5 | import com.google.gson.stream.JsonReader; 6 | 7 | import java.io.FileNotFoundException; 8 | import java.io.FileReader; 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import static java.nio.file.Files.readAllBytes; 12 | 13 | /** 14 | * Fixture to read a JSON file as String 15 | */ 16 | public class ReadJsonFile 17 | { 18 | public static String readAsString(Path path) 19 | { 20 | try 21 | { 22 | return new String(readAllBytes(path)); 23 | } 24 | catch(IOException e) 25 | { 26 | e.printStackTrace(); 27 | return "fail reading: " + path.toAbsolutePath(); 28 | } 29 | } 30 | 31 | public static JsonObject readAsJson(Path path) throws FileNotFoundException 32 | { 33 | JsonReader reader = new JsonReader(new FileReader(path.toFile())); 34 | reader.setLenient(true); 35 | return new JsonParser() 36 | .parse(reader) 37 | .getAsJsonObject(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This generated file contains a sample Java project to get you started. 5 | * For more details take a look at the Java Quickstart chapter in the Gradle 6 | * user guide available at https://docs.gradle.org/5.0/userguide/tutorial_java_projects.html 7 | */ 8 | 9 | plugins { 10 | // Apply the java plugin to add support for Java 11 | id 'java' 12 | 13 | // Apply the application plugin to add support for building an application 14 | id 'application' 15 | } 16 | 17 | repositories { 18 | // Use jcenter for resolving your dependencies. 19 | // You can declare any Maven/Ivy/file repository here. 20 | jcenter() 21 | 22 | maven { url "https://jitpack.io" } 23 | } 24 | 25 | dependencies { 26 | // implementation fileTree(dir: "./libs", includes: ['*.jar']) 27 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' 28 | implementation 'com.github.serpapi:google-search-results-java:2.0.3' 29 | } 30 | 31 | // Define the main class for the application 32 | mainClassName = 'demo.App' 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SerpApi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/test/java/serpapi/SearchArchiveApiTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import org.junit.Test; 5 | 6 | import java.util.Map; 7 | import java.util.HashMap; 8 | import static org.junit.Assert.*; 9 | 10 | public class SearchArchiveApiTest { 11 | 12 | @Test 13 | public void getSearchArchive() throws SerpApiSearchException { 14 | // skip test if no api_key provided 15 | if (System.getenv("API_KEY") == null) 16 | return; 17 | 18 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 19 | 20 | Map parameter = new HashMap<>(); 21 | parameter.put("q", "Coffee"); 22 | parameter.put("location", "Austin,Texas"); 23 | 24 | GoogleSearch search = new GoogleSearch(parameter); 25 | JsonObject result = search.getJson(); 26 | 27 | String searchID = result.get("search_metadata").getAsJsonObject().get("id").getAsString(); 28 | JsonObject archived_result = search.getSearchArchive(searchID); 29 | System.out.println(archived_result.toString()); 30 | 31 | assertEquals(searchID, archived_result.get("search_metadata").getAsJsonObject().get("id").getAsString()); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/BingSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Bing Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("q", "Coffee");
15 |  * BingSearch bing = new BingSearch(parameter, "secret api key"); 
16 |  * JsonObject data = bing.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class BingSearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public BingSearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "bing"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public BingSearch() { 36 | super("bing"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public BingSearch(Map parameter) { 44 | super(parameter, "bing"); 45 | } 46 | 47 | // end 48 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/GoogleSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Google Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("q", "Coffee");
15 |  * GoogleSearch google = new GoogleSearch(parameter, "secret api key"); 
16 |  * JsonObject data = google.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class GoogleSearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public GoogleSearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "google"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public GoogleSearch() { 36 | super("google"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public GoogleSearch(Map parameter) { 44 | super(parameter, "google"); 45 | } 46 | 47 | // end 48 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/BingSearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test bing search results 18 | */ 19 | public class BingSearchTest { 20 | 21 | BingSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | BingSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("q", "Coffee"); 38 | 39 | BingSearch result = new BingSearch(parameter); 40 | JsonObject results = result.getJson(); 41 | assertTrue(results.getAsJsonArray("organic_results").size() > 0); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/serpapi/BaiduSearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test baidu search results 18 | */ 19 | public class BaiduSearchTest { 20 | 21 | BaiduSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | BaiduSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("q", "Coffee"); 38 | 39 | BaiduSearch result = new BaiduSearch(parameter); 40 | JsonObject results = result.getJson(); 41 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/serpapi/YahooSearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test yahoo search results 18 | */ 19 | public class YahooSearchTest { 20 | 21 | YahooSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | YahooSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("p", "Coffee"); 38 | 39 | YahooSearch result = new YahooSearch(parameter); 40 | JsonObject results = result.getJson(); 41 | assertTrue(results.getAsJsonArray("organic_results").size() > 0); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/serpapi/GoogleSearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test google search results 18 | */ 19 | public class GoogleSearchTest { 20 | 21 | GoogleSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("q", "Coffee"); 38 | 39 | GoogleSearch result = new GoogleSearch(parameter); 40 | JsonObject results = result.getJson(); 41 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/serpapi/YandexSearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test yandex search results 18 | */ 19 | public class YandexSearchTest { 20 | 21 | YandexSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | YandexSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("text", "Coffee"); 38 | 39 | YandexSearch result = new YandexSearch(parameter); 40 | JsonObject results = result.getJson(); 41 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/serpapi/ParameterStringBuilder.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLEncoder; 5 | import java.util.Map; 6 | 7 | /*** 8 | * Encode parameter hash into a string 9 | * to be sent over HTTP 10 | */ 11 | public class ParameterStringBuilder 12 | { 13 | /*** 14 | * getParamsString 15 | * @param params search parameters 16 | * @return concatenated parameters 17 | * @throws UnsupportedEncodingException when none UTF-8 character is part of the parameter 18 | */ 19 | public static String getParamsString(Map params) 20 | throws UnsupportedEncodingException 21 | { 22 | StringBuilder result = new StringBuilder(); 23 | 24 | for (Map.Entry entry : params.entrySet()) 25 | { 26 | result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); 27 | result.append("="); 28 | result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); 29 | result.append("&"); 30 | } 31 | 32 | String resultString = result.toString(); 33 | return resultString.length() > 0 34 | ? resultString.substring(0, resultString.length() - 1) 35 | : resultString; 36 | } 37 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/EbaySearchTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test ebay search results 18 | */ 19 | public class EbaySearchTest { 20 | 21 | EbaySearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | EbaySearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void searchCoffee() throws SerpApiSearchException { 32 | // skip test if no api_key provided 33 | if (System.getenv("API_KEY") == null) 34 | return; 35 | 36 | Map parameter = new HashMap<>(); 37 | parameter.put("_nkw", "Coffee"); 38 | 39 | EbaySearch result = new EbaySearch(parameter); 40 | JsonObject results = result.getJson(); 41 | System.out.println(results); 42 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/serpapi/EbaySearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Ebay Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("_nkw", "Coffee");
15 |  * EbaySearch ebay = new EbaySearch(parameter, "secret api key"); 
16 |  * JsonObject data = ebay.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class EbaySearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public EbaySearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "ebay"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public EbaySearch() { 36 | super("ebay"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public EbaySearch(Map parameter) { 44 | super(parameter, "ebay"); 45 | } 46 | 47 | @Override 48 | public JsonArray getLocation(String q, Integer limit) throws SerpApiSearchException { 49 | throw new SerpApiSearchException("location is not supported for Baidu"); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/YahooSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Yahoo Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("p", "Coffee");
15 |  * YahooSearch yahoo = new YahooSearch(parameter, "secret api key"); 
16 |  * JsonObject data = yahoo.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class YahooSearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public YahooSearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "yahoo"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public YahooSearch() { 36 | super("yahoo"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public YahooSearch(Map parameter) { 44 | super(parameter, "yahoo"); 45 | } 46 | 47 | @Override 48 | public JsonArray getLocation(String q, Integer limit) throws SerpApiSearchException { 49 | throw new SerpApiSearchException("location is not supported for Baidu"); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/BaiduSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Baidu Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("q", "Coffee");
15 |  * BaiduSearch baidu = new BaiduSearch(parameter, "secret api key"); 
16 |  * JsonObject data = baidu.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class BaiduSearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public BaiduSearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "baidu"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public BaiduSearch() { 36 | super("baidu"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public BaiduSearch(Map parameter) { 44 | super(parameter, "baidu"); 45 | } 46 | 47 | @Override 48 | public JsonArray getLocation(String q, Integer limit) throws SerpApiSearchException { 49 | throw new SerpApiSearchException("location is not supported for Baidu"); 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/LocationApiTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonArray; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.mockito.ArgumentMatchers; 7 | 8 | import java.nio.file.Paths; 9 | 10 | import static org.junit.Assert.*; 11 | import static org.mockito.Mockito.*; 12 | 13 | public class LocationApiTest { 14 | 15 | @Before 16 | public void setUp() throws Exception { 17 | if (System.getenv("API_KEY") != null) { 18 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 19 | } 20 | } 21 | 22 | // @Test 23 | // public void getLocation() throws Exception { 24 | // // mock response if run on github 25 | // GoogleSearch search = new GoogleSearch(); 26 | // if (GoogleSearch.api_key_default == null) { 27 | // SerpApiHttpClient stub = mock(SerpApiHttpClient.class); 28 | // when(stub.getResults(ArgumentMatchers.anyMap())) 29 | // .thenReturn(ReadJsonFile.readAsJson(Paths.get("src/test/java/serpapi/data/location.json")).toString()); 30 | // search.search = stub; 31 | // } 32 | 33 | // // search.search = search; 34 | // JsonArray location = search.getLocation("Austin", 3); 35 | // System.out.println(location.toString()); 36 | // assertEquals("Austin, TX", location.get(0).getAsJsonObject().get("name").getAsString()); 37 | // } 38 | } -------------------------------------------------------------------------------- /src/main/java/serpapi/YandexSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import java.util.Map; 4 | import com.google.gson.JsonArray; 5 | 6 | /*** 7 | * Yandex Search Results using SerpApi 8 | * 9 | * Usage 10 | * --- 11 | *
12 |  * {@code 
13 |  * Map parameter = new HashMap<>();
14 |  * parameter.put("text", "Coffee");
15 |  * YandexSearch yandex = new YandexSearch(parameter, "secret api key"); 
16 |  * JsonObject data = yandex.getJson();
17 |  * JsonArray organic_results = data.get("organic_results").getAsJsonArray();
18 |  * }
19 |  * 
20 | */ 21 | public class YandexSearch extends SerpApiSearch { 22 | 23 | /** 24 | * Constructor 25 | * @param parameter search parameter 26 | * @param apiKey secret API key 27 | */ 28 | public YandexSearch(Map parameter, String apiKey) { 29 | super(parameter, apiKey, "yandex"); 30 | } 31 | 32 | /** 33 | * Constructor 34 | */ 35 | public YandexSearch() { 36 | super("yandex"); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * @param parameter search parameter 42 | */ 43 | public YandexSearch(Map parameter) { 44 | super(parameter, "yandex"); 45 | } 46 | 47 | @Override 48 | public JsonArray getLocation(String q, Integer limit) throws SerpApiSearchException { 49 | throw new SerpApiSearchException("location is not supported for Baidu"); 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/AccountApiTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.mockito.ArgumentMatchers; 7 | 8 | import java.nio.file.Paths; 9 | 10 | import static org.junit.Assert.*; 11 | import static org.mockito.Mockito.*; 12 | 13 | public class AccountApiTest { 14 | 15 | @Before 16 | public void setUp() throws Exception { 17 | if (System.getenv("API_KEY") != null) { 18 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 19 | } 20 | } 21 | 22 | @Test 23 | public void getAccount() throws Exception { 24 | String expected_api_key = GoogleSearch.api_key_default; 25 | 26 | // mock response if run on github 27 | GoogleSearch search = new GoogleSearch(); 28 | if (System.getenv("API_KEY") == null) { 29 | SerpApiHttpClient stub = mock(SerpApiHttpClient.class); 30 | String data = ReadJsonFile.readAsString(Paths.get("src/test/java/serpapi/data/account.json")); 31 | when(stub.getResults(ArgumentMatchers.anyMap())).thenReturn(data); 32 | search.search = stub; 33 | 34 | // fallback to default 35 | expected_api_key = "demo"; 36 | } 37 | 38 | JsonObject info = search.getAccount(); 39 | System.out.println(info.toString()); 40 | assertEquals(expected_api_key, info.getAsJsonObject().get("api_key").getAsString()); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/GoogleSearchImplementationTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import org.junit.Test; 6 | import static org.junit.Assert.*; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import org.junit.Before; 10 | 11 | /* 12 | * This Java source file was generated by the Gradle 'init' task. 13 | */ 14 | public class GoogleSearchImplementationTest { 15 | @Before 16 | public void setUp() throws Exception { 17 | if (System.getenv("API_KEY") != null) { 18 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 19 | } 20 | } 21 | 22 | @Test 23 | public void main() throws SerpApiSearchException { 24 | // skip test if no api_key provided 25 | if (System.getenv("API_KEY") == null) 26 | return; 27 | 28 | Map parameter = new HashMap<>(); 29 | parameter.put("q", "Coffee"); 30 | parameter.put("location", "Austin,Texas"); 31 | 32 | String api_key = System.getenv("API_KEY"); 33 | if (api_key == null) { 34 | api_key = "demo"; 35 | } 36 | parameter.put(GoogleSearch.API_KEY_NAME, api_key); 37 | GoogleSearch serp = new GoogleSearch(parameter); 38 | 39 | JsonObject data = serp.getJson(); 40 | JsonArray results = data.get("organic_results").getAsJsonArray(); 41 | JsonObject first_result = results.get(0).getAsJsonObject(); 42 | assertNotNull(first_result.get("title")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/serpapi/data/location.json: -------------------------------------------------------------------------------- 1 | { 2 | "location": [ 3 | { 4 | "id": "585069bdee19ad271e9bc072", 5 | "google_id": 200635, 6 | "google_parent_id": 21176, 7 | "name": "Austin, TX", 8 | "canonical_name": "Austin,TX,Texas,United States", 9 | "country_code": "US", 10 | "target_type": "DMA Region", 11 | "reach": 5560000, 12 | "gps": [ 13 | -97.7430608, 14 | 30.267153 15 | ], 16 | "keys": [ 17 | "austin", 18 | "tx", 19 | "texas", 20 | "united", 21 | "states" 22 | ] 23 | }, 24 | { 25 | "id": "585069b8ee19ad271e9ba949", 26 | "google_id": 1026201, 27 | "google_parent_id": 21176, 28 | "name": "Austin", 29 | "canonical_name": "Austin,Texas,United States", 30 | "country_code": "US", 31 | "target_type": "City", 32 | "reach": 4870000, 33 | "gps": [ 34 | -97.7430608, 35 | 30.267153 36 | ], 37 | "keys": [ 38 | "austin", 39 | "texas", 40 | "united", 41 | "states" 42 | ] 43 | }, 44 | { 45 | "id": "585069bdee19ad271e9bc05e", 46 | "google_id": 200611, 47 | "google_parent_id": 2840, 48 | "name": "Rochester, MN-Mason City, IA-Austin, MN", 49 | "canonical_name": "Rochester,MN-Mason City,IA-Austin,MN,United States", 50 | "country_code": "US", 51 | "target_type": "DMA Region", 52 | "reach": 555000, 53 | "keys": [ 54 | "rochester", 55 | "mn", 56 | "mason", 57 | "city", 58 | "ia", 59 | "austin", 60 | "mn", 61 | "united", 62 | "states" 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /demo/src/main/java/demo/App.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Java source file was generated by the Gradle 'init' task. 3 | */ 4 | package demo; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonArray; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonObject; 10 | 11 | import serpapi.SerpApiSearchException; 12 | import serpapi.GoogleSearch; 13 | import java.util.Map; 14 | import java.util.HashMap; 15 | 16 | public class App { 17 | public static void main(String[] args) throws SerpApiSearchException { 18 | if(args.length != 1) { 19 | System.out.println("Usage: app "); 20 | System.exit(1); 21 | } 22 | 23 | String location = "Austin,Texas"; 24 | System.out.println("find the first Coffee in " + location); 25 | 26 | // parameters 27 | Map parameter = new HashMap<>(); 28 | parameter.put("q", "Coffee"); 29 | parameter.put("location", location); 30 | parameter.put(GoogleSearch.API_KEY_NAME, args[0]); 31 | 32 | // Create search 33 | GoogleSearch search = new GoogleSearch(parameter); 34 | 35 | try { 36 | // Execute search 37 | JsonObject data = search.getJson(); 38 | // Decode response 39 | JsonArray results = data.get("local_results").getAsJsonObject().get("places").getAsJsonArray(); 40 | JsonObject first_result = results.get(0).getAsJsonObject(); 41 | System.out.println("first coffee shop: " + first_result.get("title").getAsString() + " found on Google in " + location); 42 | } catch (SerpApiSearchException e) { 43 | System.out.println("oops exception detected!"); 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/serpapi/GoogleSearchClientTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.net.HttpURLConnection; 7 | import java.util.HashMap; 8 | 9 | import java.nio.file.Paths; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Test HTTP search 15 | */ 16 | public class GoogleSearchClientTest { 17 | SerpApiHttpClient search; 18 | private HashMap parameter; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | search = new SerpApiHttpClient("/search"); 23 | 24 | parameter = new HashMap<>(); 25 | parameter.put("q", "Coffee"); 26 | parameter.put("location", "Austin, Texas"); 27 | parameter.put("output", "json"); 28 | 29 | String api_key = System.getenv("API_KEY"); 30 | if (api_key == null) { 31 | GoogleSearch.api_key_default = "demo"; 32 | parameter.put(GoogleSearch.API_KEY_NAME, "demo"); 33 | } else { 34 | parameter.put(GoogleSearch.API_KEY_NAME, api_key); 35 | } 36 | } 37 | 38 | @Test 39 | public void buildConnection() throws SerpApiSearchException { 40 | HttpURLConnection connection = search.buildConnection("/search", parameter); 41 | assertEquals("https://serpapi.com/search?output=json&q=Coffee&api_key=" + GoogleSearch.api_key_default 42 | + "&location=Austin%2C+Texas", connection.getURL().toString()); 43 | } 44 | 45 | @Test 46 | public void triggerSerpApiClientException() throws Exception { 47 | try { 48 | String content = ReadJsonFile.readAsJson(Paths.get("src/test/java/serpapi/data/error_sample.json")).toString(); 49 | search.triggerSerpApiClientException(content); 50 | } catch (Exception ex) { 51 | assertEquals(SerpApiSearchException.class, ex.getClass()); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/serpapi/SerpApiClientTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test main class 18 | */ 19 | public class SerpApiClientTest { 20 | 21 | GoogleSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void buildParameter() throws SerpApiSearchException { 32 | Map parameter = new HashMap<>(); 33 | parameter.put("q", "Coffee"); 34 | parameter.put("location", "Austin, Texas"); 35 | search = new GoogleSearch(parameter); 36 | search.buildQuery("/search", "html"); 37 | assertEquals(search.parameter.get("source"), "java"); 38 | assertEquals(search.parameter.get(GoogleSearch.API_KEY_NAME), GoogleSearch.api_key_default); 39 | } 40 | 41 | @Test 42 | public void builParameterForInstance() throws SerpApiSearchException { 43 | GoogleSearch search = new GoogleSearch(); 44 | search.buildQuery("/search", "json"); 45 | assertEquals(search.parameter.get("source"), "java"); 46 | assertEquals(search.parameter.get("output"), "json"); 47 | assertEquals(search.parameter.get(GoogleSearch.API_KEY_NAME), GoogleSearch.api_key_default); 48 | } 49 | 50 | @Test 51 | public void getApiKey() throws Exception { 52 | GoogleSearch.api_key_default = "abc"; 53 | assertEquals("abc", GoogleSearch.getApiKey()); 54 | } 55 | 56 | @Test 57 | public void asJson() throws Exception { 58 | GoogleSearch search = new GoogleSearch(); 59 | JsonObject expectation = new JsonObject(); 60 | expectation.add("status", new JsonPrimitive("ok")); 61 | assertEquals(expectation, search.asJson("{\"status\": \"ok\"}")); 62 | } 63 | 64 | @Test 65 | public void getHtml() throws Exception { 66 | SerpApiHttpClient search = mock(SerpApiHttpClient.class); 67 | 68 | String htmlContent = ReadJsonFile.readAsString(Paths.get("src/test/java/serpapi/data/search_coffee_sample.html")); 69 | when(search.getResults(ArgumentMatchers.anyMap())).thenReturn(htmlContent); 70 | 71 | Map parameter = new HashMap<>(); 72 | parameter.put("q", "Coffee"); 73 | parameter.put("location", "Austin, Texas"); 74 | GoogleSearch result = new GoogleSearch(parameter); 75 | result.search = search; 76 | 77 | assertEquals(htmlContent, result.getHtml()); 78 | } 79 | 80 | @Test 81 | public void getJson() throws Exception { 82 | Map parameter = new HashMap<>(); 83 | parameter.put("q", "Coffee"); 84 | parameter.put("location", "Austin, Texas"); 85 | 86 | SerpApiHttpClient stub = mock(SerpApiHttpClient.class); 87 | when(stub.getResults(ArgumentMatchers.anyMap())) 88 | .thenReturn(ReadJsonFile.readAsJson(Paths.get("src/test/java/serpapi/data/search_coffee_sample.json")).toString()); 89 | 90 | GoogleSearch search = new GoogleSearch(parameter); 91 | search.search = stub; 92 | 93 | assertEquals(3, search.getJson().getAsJsonArray("local_results").size()); 94 | } 95 | 96 | @Test 97 | public void searchCoffee() throws SerpApiSearchException { 98 | // skip test if no api_key provided 99 | if(System.getenv("API_KEY") == null) 100 | return; 101 | 102 | Map parameter = new HashMap<>(); 103 | parameter.put("q", "Coffee"); 104 | parameter.put("location", "Austin, Texas, United States"); 105 | parameter.put("hl", "en"); 106 | parameter.put("gl", "us"); 107 | parameter.put("google_domain", "google.com"); 108 | // parameter.put("api_key", "demo"); 109 | parameter.put("safe", "active"); 110 | parameter.put("start", "10"); 111 | parameter.put("num", "10"); 112 | parameter.put("device", "desktop"); 113 | 114 | GoogleSearch result = new GoogleSearch(parameter); 115 | JsonObject results = result.getJson(); 116 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/serpapi/GoogleSearchFullTest.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonPrimitive; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.ArgumentMatchers; 8 | 9 | import java.nio.file.Paths; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Test main class 18 | */ 19 | public class GoogleSearchFullTest { 20 | 21 | GoogleSearch search; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | if (System.getenv("API_KEY") != null) { 26 | GoogleSearch.api_key_default = System.getenv("API_KEY"); 27 | } 28 | } 29 | 30 | @Test 31 | public void buildParameter() throws SerpApiSearchException { 32 | Map parameter = new HashMap<>(); 33 | parameter.put("q", "Coffee"); 34 | parameter.put("location", "Austin, Texas"); 35 | search = new GoogleSearch(parameter); 36 | search.buildQuery("/search", "html"); 37 | assertEquals(search.parameter.get("source"), "java"); 38 | assertEquals(search.parameter.get(GoogleSearch.API_KEY_NAME), GoogleSearch.api_key_default); 39 | } 40 | 41 | @Test 42 | public void builParameterForInstance() throws SerpApiSearchException { 43 | GoogleSearch search = new GoogleSearch(); 44 | search.buildQuery("/search", "json"); 45 | assertEquals(search.parameter.get("source"), "java"); 46 | assertEquals(search.parameter.get("output"), "json"); 47 | assertEquals(search.parameter.get(GoogleSearch.API_KEY_NAME), GoogleSearch.api_key_default); 48 | } 49 | 50 | @Test 51 | public void getApiKey() throws Exception { 52 | GoogleSearch.api_key_default = "abc"; 53 | assertEquals("abc", GoogleSearch.getApiKey()); 54 | } 55 | 56 | @Test 57 | public void asJson() throws Exception { 58 | GoogleSearch search = new GoogleSearch(); 59 | JsonObject expectation = new JsonObject(); 60 | expectation.add("status", new JsonPrimitive("ok")); 61 | assertEquals(expectation, search.asJson("{\"status\": \"ok\"}")); 62 | } 63 | 64 | @Test 65 | public void getHtml() throws Exception { 66 | SerpApiHttpClient search = mock(SerpApiHttpClient.class); 67 | 68 | String htmlContent = ReadJsonFile.readAsString(Paths.get("src/test/java/serpapi/data/search_coffee_sample.html")); 69 | when(search.getResults(ArgumentMatchers.anyMap())).thenReturn(htmlContent); 70 | 71 | Map parameter = new HashMap<>(); 72 | parameter.put("q", "Coffee"); 73 | parameter.put("location", "Austin, Texas"); 74 | GoogleSearch result = new GoogleSearch(parameter); 75 | result.search = search; 76 | 77 | assertEquals(htmlContent, result.getHtml()); 78 | } 79 | 80 | @Test 81 | public void getJson() throws Exception { 82 | Map parameter = new HashMap<>(); 83 | parameter.put("q", "Coffee"); 84 | parameter.put("location", "Austin, Texas"); 85 | 86 | SerpApiHttpClient stub = mock(SerpApiHttpClient.class); 87 | when(stub.getResults(ArgumentMatchers.anyMap())).thenReturn( 88 | ReadJsonFile.readAsJson(Paths.get("src/test/java/serpapi/data/search_coffee_sample.json")).toString()); 89 | 90 | GoogleSearch search = new GoogleSearch(parameter); 91 | search.search = stub; 92 | 93 | assertEquals(3, search.getJson().getAsJsonArray("local_results").size()); 94 | } 95 | 96 | @Test 97 | public void searchCoffee() throws SerpApiSearchException { 98 | // skip test if no api_key provided 99 | if (System.getenv("API_KEY") == null) 100 | return; 101 | 102 | Map parameter = new HashMap<>(); 103 | parameter.put("q", "Coffee"); 104 | parameter.put("location", "Austin, Texas, United States"); 105 | parameter.put("hl", "en"); 106 | parameter.put("gl", "us"); 107 | parameter.put("google_domain", "google.com"); 108 | // parameter.put("api_key", "demo"); 109 | parameter.put("safe", "active"); 110 | parameter.put("start", "10"); 111 | parameter.put("num", "10"); 112 | parameter.put("device", "desktop"); 113 | 114 | GoogleSearch result = new GoogleSearch(parameter); 115 | JsonObject results = result.getJson(); 116 | assertTrue(results.getAsJsonArray("organic_results").size() > 5); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/serpapi/SerpApiSearch.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | 8 | import java.util.Map; 9 | import java.util.HashMap; 10 | 11 | /** 12 | * SerpApiSearch wraps HTTP interaction with the service serpapi.com 13 | */ 14 | public class SerpApiSearch extends Exception { 15 | /** 16 | * Set of constant 17 | */ 18 | public static final String API_KEY_NAME = "api_key"; 19 | 20 | /** 21 | * default static key 22 | */ 23 | public static String api_key_default; 24 | 25 | /** 26 | * user secret API key 27 | */ 28 | protected String api_key; 29 | 30 | /** 31 | * Current search engine 32 | */ 33 | protected String engine; 34 | 35 | /** 36 | * search parameters 37 | */ 38 | public Map parameter; 39 | 40 | // initialize gson 41 | private static Gson gson = new Gson(); 42 | 43 | /** 44 | * https search implementation for Java 7+ 45 | */ 46 | public SerpApiHttpClient search; 47 | 48 | /*** 49 | * Constructor 50 | * 51 | * @param parameter user search 52 | * @param api_key secret user API key 53 | * @param engine service like: google, naver, yahoo... 54 | */ 55 | public SerpApiSearch(Map parameter, String api_key, String engine) { 56 | this.parameter = parameter; 57 | this.api_key = api_key; 58 | this.engine = engine; 59 | } 60 | 61 | /*** 62 | * Constructor 63 | * 64 | * @param parameter user search 65 | * @param engine service like: google, yahoo, bing... 66 | */ 67 | public SerpApiSearch(Map parameter, String engine) { 68 | this.parameter = parameter; 69 | this.engine = engine; 70 | } 71 | 72 | /*** 73 | * Constructor with no parameter 74 | * @param engine service like: google, bing, yahoo... 75 | */ 76 | public SerpApiSearch(String engine) { 77 | this.parameter = new HashMap(); 78 | this.engine = engine; 79 | } 80 | 81 | /** 82 | * Constructor 83 | * 84 | * @param api_key secret API key 85 | * @param engine service like: google, bing, yahoo... 86 | */ 87 | public SerpApiSearch(String api_key, String engine) { 88 | this.api_key = api_key; 89 | this.engine = engine; 90 | } 91 | 92 | /*** 93 | * Build a serp API query by expanding existing parameter 94 | * 95 | * @param path backend HTTP path 96 | * @param output type of output format (json, html, json_with_images) 97 | * @return format parameter hash map 98 | * @throws SerpApiSearchException wraps backend error message 99 | */ 100 | public Map buildQuery(String path, String output) throws SerpApiSearchException { 101 | // Initialize search if not done 102 | if (search == null) { 103 | this.search = new SerpApiHttpClient(path); 104 | this.search.setHttpConnectionTimeout(6000); 105 | } else { 106 | this.search.path = path; 107 | } 108 | 109 | // Set current programming language 110 | this.parameter.put("source", "java"); 111 | 112 | // Set api_key 113 | if (this.parameter.get(API_KEY_NAME) == null) { 114 | if (this.api_key != null) { 115 | this.parameter.put(API_KEY_NAME, this.api_key); 116 | } else if (getApiKey() != null) { 117 | this.parameter.put(API_KEY_NAME, getApiKey()); 118 | } else { 119 | throw new SerpApiSearchException(API_KEY_NAME + " is not defined"); 120 | } 121 | } 122 | 123 | this.parameter.put("engine", this.engine); 124 | 125 | // Set output format 126 | this.parameter.put("output", output); 127 | 128 | return this.parameter; 129 | } 130 | 131 | /** 132 | * @return current secret api key 133 | */ 134 | public static String getApiKey() { 135 | return api_key_default; 136 | } 137 | 138 | /*** 139 | * Get HTML output 140 | * 141 | * @return raw HTML response from the search engine for custom parsing 142 | * @throws SerpApiSearchException wraps backend error message 143 | */ 144 | public String getHtml() throws SerpApiSearchException { 145 | Map query = buildQuery("/search", "html"); 146 | return search.getResults(query); 147 | } 148 | 149 | /*** 150 | * Get JSON output 151 | * 152 | * @return JsonObject parent node 153 | * @throws SerpApiSearchException wraps backend error message 154 | */ 155 | public JsonObject getJson() throws SerpApiSearchException { 156 | Map query = buildQuery("/search", "json"); 157 | return asJson(search.getResults(query)); 158 | } 159 | 160 | /*** 161 | * Convert HTTP content to JsonValue 162 | * 163 | * @param content raw JSON HTTP response 164 | * @return JsonObject created by gson parser 165 | */ 166 | public JsonObject asJson(String content) { 167 | JsonElement element = gson.fromJson(content, JsonElement.class); 168 | return element.getAsJsonObject(); 169 | } 170 | 171 | /*** 172 | * @return http search 173 | */ 174 | public SerpApiHttpClient getClient() { 175 | return this.search; 176 | } 177 | 178 | /*** 179 | * Get location 180 | * 181 | * @param q query 182 | * @param limit number of location 183 | * @return JsonObject location using Location API 184 | * @throws SerpApiSearchException wraps backend error message 185 | */ 186 | public JsonArray getLocation(String q, Integer limit) throws SerpApiSearchException { 187 | Map query = buildQuery("/locations.json", "json"); 188 | query.remove("output"); 189 | query.remove(API_KEY_NAME); 190 | query.put("q", q); 191 | query.put("limit", limit.toString()); 192 | String s = search.getResults(query); 193 | return gson.fromJson(s, JsonArray.class); 194 | } 195 | 196 | /*** 197 | * Get search result from the Search Archive API 198 | * 199 | * @param searchID archived search result = search_metadata.id 200 | * @return JsonObject search result 201 | * @throws SerpApiSearchException wraps backend error message 202 | */ 203 | public JsonObject getSearchArchive(String searchID) throws SerpApiSearchException { 204 | Map query = buildQuery("/searches/" + searchID + ".json", "json"); 205 | query.remove("output"); 206 | query.remove("q"); 207 | return asJson(search.getResults(query)); 208 | } 209 | 210 | /*** 211 | * Get account information using Account API 212 | * 213 | * @return JsonObject account information 214 | * @throws SerpApiSearchException wraps backend error message 215 | */ 216 | public JsonObject getAccount() throws SerpApiSearchException { 217 | Map query = buildQuery("/account", "json"); 218 | query.remove("output"); 219 | query.remove("q"); 220 | return asJson(search.getResults(query)); 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /src/main/java/serpapi/SerpApiHttpClient.java: -------------------------------------------------------------------------------- 1 | package serpapi; 2 | 3 | import javax.net.ssl.*; 4 | import java.io.*; 5 | import java.net.HttpURLConnection; 6 | import java.net.URL; 7 | import java.security.KeyManagementException; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.cert.X509Certificate; 10 | import java.util.Map; 11 | 12 | import com.google.gson.Gson; 13 | import com.google.gson.JsonObject; 14 | import com.google.gson.JsonPrimitive; 15 | 16 | /** 17 | * HTTPS search for Serp API 18 | */ 19 | public class SerpApiHttpClient { 20 | // http request configuration 21 | private int httpConnectionTimeout; 22 | private int httpReadTimeout; 23 | 24 | /** 25 | * current API version 26 | */ 27 | public static String VERSION = "2.0.3"; 28 | 29 | /** 30 | * backend service 31 | */ 32 | public static String BACKEND = "https://serpapi.com"; 33 | 34 | /** 35 | * initialize gson 36 | */ 37 | private static Gson gson = new Gson(); 38 | 39 | /** 40 | * current backend HTTP path 41 | */ 42 | public String path; 43 | 44 | /*** 45 | * Constructor 46 | * @param path HTTP url path 47 | */ 48 | public SerpApiHttpClient(String path) { 49 | this.path = path; 50 | } 51 | 52 | /*** 53 | * Build URL 54 | * 55 | * @param path url end point 56 | * @param parameter search parameter map like: { "q": "coffee", "location": "Austin, TX"} 57 | * @return httpUrlConnection 58 | * @throws SerpApiSearchException wraps error message 59 | */ 60 | protected HttpURLConnection buildConnection(String path, Map parameter) throws SerpApiSearchException { 61 | HttpURLConnection con; 62 | try { 63 | allowHTTPS(); 64 | String query = ParameterStringBuilder.getParamsString(parameter); 65 | URL url = new URL(BACKEND + path + "?" + query); 66 | con = (HttpURLConnection) url.openConnection(); 67 | con.setRequestMethod("GET"); 68 | } catch (IOException e) { 69 | throw new SerpApiSearchException(e); 70 | } catch (NoSuchAlgorithmException e) { 71 | e.printStackTrace(); 72 | throw new SerpApiSearchException(e); 73 | } catch (KeyManagementException e) { 74 | e.printStackTrace(); 75 | throw new SerpApiSearchException(e); 76 | } 77 | 78 | String outputFormat = parameter.get("output"); 79 | if (outputFormat == null) { 80 | if (path.startsWith("/search?")) { 81 | throw new SerpApiSearchException("output format must be defined: " + path); 82 | } 83 | } else if (outputFormat.startsWith("json")) { 84 | con.setRequestProperty("Content-Type", "application/json"); 85 | } 86 | 87 | // TODO Enable to set different timeout 88 | con.setConnectTimeout(getHttpConnectionTimeout()); 89 | con.setReadTimeout(getHttpReadTimeout()); 90 | 91 | con.setDoOutput(true); 92 | return con; 93 | } 94 | 95 | private void allowHTTPS() throws NoSuchAlgorithmException, KeyManagementException { 96 | TrustManager[] trustAllCerts; 97 | trustAllCerts = new TrustManager[] { new X509TrustManager() { 98 | public X509Certificate[] getAcceptedIssuers() { 99 | return null; 100 | } 101 | 102 | public void checkClientTrusted(X509Certificate[] certs, String authType) { 103 | } 104 | 105 | public void checkServerTrusted(X509Certificate[] certs, String authType) { 106 | } 107 | 108 | } }; 109 | 110 | SSLContext sc = SSLContext.getInstance("SSL"); 111 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 112 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 113 | 114 | // Create all-trusting host name verifier 115 | HostnameVerifier allHostsValid = new HostnameVerifier() { 116 | public boolean verify(String hostname, SSLSession session) { 117 | return true; 118 | } 119 | }; 120 | // Install the all-trusting host verifier 121 | HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); 122 | /* 123 | * end of the fix 124 | */ 125 | } 126 | 127 | /*** 128 | * Get results 129 | * 130 | * @param parameter user search parameters 131 | * @return http response body 132 | * @throws SerpApiSearchException wraps error message 133 | */ 134 | public String getResults(Map parameter) throws SerpApiSearchException { 135 | HttpURLConnection con = buildConnection(this.path, parameter); 136 | 137 | // Get HTTP status 138 | int statusCode = -1; 139 | // Hold response stream 140 | InputStream is = null; 141 | // Read buffer 142 | BufferedReader in = null; 143 | try { 144 | statusCode = con.getResponseCode(); 145 | 146 | if (statusCode == 200) { 147 | is = con.getInputStream(); 148 | } else { 149 | is = con.getErrorStream(); 150 | } 151 | 152 | Reader reader = new InputStreamReader(is); 153 | in = new BufferedReader(reader); 154 | } catch (IOException e) { 155 | throw new SerpApiSearchException(e); 156 | } 157 | 158 | String inputLine; 159 | StringBuffer content = new StringBuffer(); 160 | try { 161 | while ((inputLine = in.readLine()) != null) { 162 | content.append(inputLine); 163 | } 164 | in.close(); 165 | } catch (IOException e) { 166 | throw new SerpApiSearchException(e); 167 | } 168 | 169 | // Disconnect 170 | con.disconnect(); 171 | 172 | if (statusCode != 200) { 173 | triggerSerpApiClientException(content.toString()); 174 | } 175 | return content.toString(); 176 | } 177 | 178 | /** 179 | * trigger a exception on error 180 | * @param content raw JSON response from serpapi.com 181 | * @throws SerpApiSearchException wraps error message 182 | */ 183 | protected void triggerSerpApiClientException(String content) throws SerpApiSearchException { 184 | String errorMessage; 185 | try { 186 | JsonObject element = gson.fromJson(content, JsonObject.class); 187 | errorMessage = element.get("error").getAsString(); 188 | } catch (Exception e) { 189 | throw new AssertionError("invalid response format: " + content); 190 | } 191 | throw new SerpApiSearchException(errorMessage); 192 | } 193 | 194 | /** 195 | * @return current HTTP connection timeout 196 | */ 197 | public int getHttpConnectionTimeout() { 198 | return httpConnectionTimeout; 199 | } 200 | 201 | /** 202 | * @param httpConnectionTimeout set HTTP connection timeout 203 | */ 204 | public void setHttpConnectionTimeout(int httpConnectionTimeout) { 205 | this.httpConnectionTimeout = httpConnectionTimeout; 206 | } 207 | 208 | /** 209 | * @return current HTTP read timeout 210 | */ 211 | public int getHttpReadTimeout() { 212 | return httpReadTimeout; 213 | } 214 | 215 | /** 216 | * @param httpReadTimeout set HTTP read timeout 217 | */ 218 | public void setHttpReadTimeout(int httpReadTimeout) { 219 | this.httpReadTimeout = httpReadTimeout; 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/serpapi/data/search_coffee_sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | SERP API: Google Search Results API 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | 46 |

SERP API

47 |
48 | 49 |
50 | 51 |
52 |

Signed out successfully.

53 |
54 |
55 | 56 |
57 |

Google Search Results API

58 |

Real time API to get Google search results with ease. We've solved the issues of having to manage proxies, to solve CAPTCHAs and to parse rich structured data.

59 |

Developers first, as simple as firing HTTP GETs to:

60 |
61 |
62 | 63 |
64 |
65 |

Real Time and True Results

66 |

Each request runs a full browser even solving complex JS captchas and are processed immediately (except in case of unplanned charge). It guarantees that you get what users truly see.

67 | 68 |

Accurate Locations

69 |

Teleport yourself to wherever you want with the "location" parameter. We use both use Google geolocated encrypted params and we route your request via one of our numerous proxies the closest to your desired location to ensure accuracy. (Read More)

70 | 71 |

Advanced JSON Parsing

72 |

Including results from: organic, stories, shopping, direct answer, knowledge graph, maps, and local. And structured data for each: links, addresses, tweets, prices, thumbnails, ratings, reviews, rich snippets, and more.

73 |
74 | 75 |
76 |

All Countries and Languages

77 |

Supports all Google languages via "hl" param (Read More), all Google countries via "gl" param (Read More), and all Google domains (Read More)

78 | 79 |

Easy Integration

80 |

Regular HTTP GET requests will get you the results. Don't worry about proxies and Google changes. Just build your app. Couldn't be easier.

81 | 82 |

High Reliability

83 |

In the last 24 hours, API requests had a 98.67% success rate and were completed in an average of 28.60 seconds.

84 | 85 |

Simple Pricing

86 |

1,000 credits for only $9.97. 1 credit = 1 Google search query. (Add Credits)

87 |
88 |
89 | 90 |
91 |
92 |

Disclaimer: SERP API is in beta, and the API might change.

93 |
94 |
95 | 96 |
97 |

© 2017 SERP API, Inc. - contact@serpapi.com

98 |
99 |
100 | 101 | 102 | 103 | 104 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Search Results JAVA API 2 | 3 | ![test](https://github.com/serpapi/google-search-results-java/workflows/test/badge.svg) 4 | 5 | This Java package enables to scrape and parse Google, Bing and Baidu search results using [SerpApi](https://serpapi.com). Feel free to fork this repository to add more backends. 6 | 7 | This project is an implementation of SerpApi in Java 7. 8 | This code depends on GSON for efficient JSON processing. 9 | The HTTP response are converted to JSON Object. 10 | 11 | An example is provided in the test. 12 | @see src/test/java/GoogleSearchImplementationTest.java 13 | 14 | [The full documentation is available here.](https://serpapi.com/search-api) 15 | 16 | ## Requirements 17 | 18 | Runtime: 19 | - Java / JDK 8+ (https://www.java.com/en/download/) 20 | Older version of java do not support HTTPS protocol. 21 | The SSLv3 is buggy which leads to Java raising this exception: javax.net.ssl.SSLHandshakeException 22 | 23 | For development: 24 | - Gradle 6.7+ (https://gradle.org/install/) 25 | 26 | ## Maven / Gradle support 27 | 28 | Edit your build.gradle file 29 | ```java 30 | repositories { 31 | maven { url "https://jitpack.io" } 32 | } 33 | 34 | dependencies { 35 | implementation 'com.github.serpapi:google-search-results-java:2.0.2' 36 | } 37 | ``` 38 | 39 | To list all the version available. 40 | https://jitpack.io/api/builds/com.github.serpapi/google-search-results-java 41 | 42 | Note: jitpack.io enables to download maven package directly from github release. 43 | 44 | ## Quick start 45 | 46 | To get started with this project in Java. 47 | We provided a fully working example. 48 | ```bash 49 | git clone https://github.com/serpapi/google_search_results_java.git 50 | cd google_search_results_java/demo 51 | make run api_key= 52 | ``` 53 | Note: You need an account with SerpApi to obtain this key from: https://serpapi.com/dashboard 54 | 55 | file: demo/src/main/java/demo/App.java 56 | ```java 57 | public class App { 58 | public static void main(String[] args) throws SerpApiSearchException { 59 | if(args.length != 1) { 60 | System.out.println("Usage: app "); 61 | System.exit(1); 62 | } 63 | 64 | String location = "Austin,Texas"; 65 | System.out.println("find the first Coffee in " + location); 66 | 67 | // parameters 68 | Map parameter = new HashMap<>(); 69 | parameter.put("q", "Coffee"); 70 | parameter.put("location", location); 71 | parameter.put(GoogleSearch.SERP_API_KEY_NAME, args[0]); 72 | 73 | // Create search 74 | GoogleSearch search = new GoogleSearch(parameter); 75 | 76 | try { 77 | // Execute search 78 | JsonObject data = search.getJson(); 79 | 80 | // Decode response 81 | JsonArray results = (JsonArray) data.get("local_results"); 82 | JsonObject first_result = results.get(0).getAsJsonObject(); 83 | System.out.println("first coffee: " + first_result.get("title").getAsString() + " in " + location); 84 | } catch (SerpApiSearchException e) { 85 | System.out.println("oops exception detected!"); 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | This example runs a search about "coffee" using your secret api key. 93 | 94 | The Serp API service (backend) 95 | - searches on Google using the query: q = "coffee" 96 | - parses the messy HTML responses 97 | - return a standardized JSON response 98 | The class GoogleSearch 99 | - Format the request to Serp API server 100 | - Execute GET http request 101 | - Parse JSON into Ruby Hash using JSON standard library provided by Ruby 102 | Et voila.. 103 | 104 | Alternatively, you can search: 105 | - Bing using BingSearch class 106 | - Baidu using BaiduSearch class 107 | 108 | See the playground to generate your code. 109 | https://serpapi.com/playground 110 | 111 | ## Example 112 | * [How to set SERP API key](#how-to-set-serp-api-key) 113 | * [Search API capability](#search-api-capability) 114 | * [Example by specification](#example-by-specification) 115 | * [Location API](#location-api) 116 | * [Search Archive API](#search-archive-api) 117 | * [Account API](#account-api) 118 | 119 | ## How to set SERP API key 120 | The Serp API key can be set globally using a singleton pattern. 121 | ```java 122 | GoogleSearch.serp_api_key_default = "Your Private Key" 123 | search = GoogleSearch(parameter) 124 | ``` 125 | Or the Serp API key can be provided for each query. 126 | 127 | ```java 128 | search = GoogleSearch(parameter, "Your Private Key") 129 | ``` 130 | 131 | ## Example with all params and all outputs 132 | 133 | ```java 134 | query_parameter = { 135 | "q": "query", 136 | "google_domain": "Google Domain", 137 | "location": "Location Requested", 138 | "device": device, 139 | "hl": "Google UI Language", 140 | "gl": "Google Country", 141 | "safe": "Safe Search Flag", 142 | "num": "Number of Results", 143 | "start": "Pagination Offset", 144 | "serp_api_key": "Your SERP API Key", 145 | "tbm": "nws|isch|shop", 146 | "tbs": "custom to be search criteria", 147 | "async": true|false, // allow async request - non-blocker 148 | "output": "json|html", // output format 149 | } 150 | 151 | query = GoogleSearch.new(query_parameter) 152 | query.parameter.put("location", "Austin,Texas") 153 | 154 | String html_results = query.getHtml() 155 | JsonObject json_results = query.getJson() 156 | ``` 157 | 158 | ### Example by specification 159 | 160 | We love true open source, continuous integration and Test Drive Development (TDD). 161 | We are using RSpec to test [our infrastructure around the clock](https://travis-ci.org/serpapi/google-search-results-ruby) to achieve the best QoS (Quality Of Service). 162 | 163 | The directory test/ includes specification/examples. 164 | 165 | To run the test: 166 | ```gradle test``` 167 | 168 | 169 | ### Location API 170 | 171 | ```java 172 | GoogleSearch search = new GoogleSearch(new HashMap parameter = new HashMap<>(); 183 | parameter.put("q", "Coffee"); 184 | parameter.put("location", "Austin,Texas"); 185 | 186 | GoogleSearch search = new GoogleSearch(parameter); 187 | JsonObject result = search.getJson(); 188 | int search_id = result.get("search_metadata").getAsJsonObject().get("id").getAsInt(); 189 | ``` 190 | 191 | Now let retrieve the previous search from the archive. 192 | ```java 193 | JsonObject archived_result = search.getSearchArchive(search_id); 194 | System.out.println(archived_result.toString()); 195 | ``` 196 | it prints the search from the archive. 197 | 198 | ### Account API 199 | Get account API 200 | ```java 201 | GoogleSearch.serp_api_key_default = "Your Private Key" 202 | GoogleSearch search = new GoogleSearch(); 203 | JsonObject info = search.getAccount(); 204 | System.out.println(info.toString()); 205 | ``` 206 | it prints your account information. 207 | 208 | ## Build project 209 | 210 | ### How to build from the source ? 211 | 212 | You must clone this repository. 213 | ```bash 214 | git clone https://github.com/serpapi/google_search_results_java.git 215 | ``` 216 | Build the jar file. 217 | ```bash 218 | gradle build 219 | ``` 220 | Copy the jar to your project lib/ directory. 221 | ```bash 222 | cp build/libs/google_search_results_java.jar path/to/yourproject/lib 223 | ``` 224 | 225 | ### How to test ? 226 | 227 | ```bash 228 | make test 229 | ``` 230 | 231 | ### Conclusion 232 | 233 | This service supports Google Images, News, Shopping. 234 | To enable a type of search, the field tbm (to be matched) must be set to: 235 | 236 | * isch: Google Images API. 237 | * nws: Google News API. 238 | * shop: Google Shopping API. 239 | * any other Google service should work out of the box. 240 | * (no tbm parameter): regular Google Search. 241 | 242 | [The full documentation is available here.](https://serpapi.com/search-api) 243 | 244 | Issue 245 | --- 246 | ### SSL handshake error. 247 | 248 | #### symptom 249 | 250 | javax.net.ssl.SSLHandshakeException 251 | 252 | #### cause 253 | SerpApi is using HTTPS / SSLv3. Older JVM version do not support this protocol because it's more recent. 254 | 255 | #### solution 256 | Upgrade java to 1.8_201+ (which is recommended by Oracle). 257 | 258 | * On OSX you can switch versino of Java. 259 | ```sh 260 | export JAVA_HOME=`/usr/libexec/java_home -v 1.8.0_201` 261 | java -version 262 | ``` 263 | 264 | * On Windows manually upgrade your JDK / JVM to the latest. 265 | 266 | * On Linux, Oracle JDK 8 (1.8_151+) seems to work fine. 267 | see: https://travis-ci.org/serpapi/google-search-results-java 268 | 269 | Changelog 270 | --- 271 | - 2.0.1 update gradle 6.7.1 272 | - 2.0 refractor API : suffix SearchResults renamed Search 273 | - 1.4 Add support for Yandex, Yahoo, Ebay 274 | - 1.3 Add support for Bing and Baidu 275 | - 1.2 Add support for location API, account API, search API 276 | 277 | Source 278 | --- 279 | * http://www.baeldung.com/java-http-request 280 | * https://github.com/google/gson 281 | 282 | Author 283 | --- 284 | Victor Benarbia - victor@serpapi.com 285 | -------------------------------------------------------------------------------- /src/test/java/basic_search_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "search_metadata": { 3 | "id": "5c91926661a1347a7a2ebe3a", 4 | "status": "Success", 5 | "created_at": "2019-03-20 01:07:50 UTC", 6 | "processed_at": "2019-03-20 01:07:50 UTC", 7 | "google_url": "https://www.google.com/search?q=Coffee&oq=Coffee&uule=w+CAIQICIaQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM&hl=en&gl=us&num=10&safe=active&start=10&sourceid=chrome&ie=UTF-8", 8 | "total_time_taken": 10.29 9 | }, 10 | "search_parameters": { 11 | "q": "Coffee", 12 | "location_requested": "Austin, Texas, United States", 13 | "location_used": "Austin,Texas,United States", 14 | "google_domain": "google.com", 15 | "hl": "en", 16 | "gl": "us", 17 | "safe": "active", 18 | "start": "10", 19 | "num": "10", 20 | "device": "desktop" 21 | }, 22 | "search_information": { 23 | "total_results": 23620000000, 24 | "time_taken_displayed": 0.6, 25 | "query_displayed": "Coffee" 26 | }, 27 | "organic_results": [ 28 | { 29 | "position": 1, 30 | "title": "What is Coffee? - National Coffee Association", 31 | "link": "http://www.ncausa.org/about-coffee/what-is-coffee", 32 | "displayed_link": "www.ncausa.org › About Coffee › What is Coffee?", 33 | "snippet": "Coffee trees are pruned short to conserve their energy and aid in harvesting, but can grow to more than 30 feet (9 meters) high. Each tree is covered with green, ...", 34 | "cached_page_link": "http://webcache.googleusercontent.com/search?q=cache:eukMDyc_TlwJ:www.ncausa.org/about-coffee/what-is-coffee+&cd=11&hl=en&ct=clnk&gl=us" 35 | }, 36 | { 37 | "position": 2, 38 | "title": "The History of Coffee - National Coffee Association", 39 | "link": "http://www.ncausa.org/about-coffee/history-of-coffee", 40 | "displayed_link": "www.ncausa.org › About Coffee › History of Coffee", 41 | "snippet": "No one knows exactly how or when coffee was discovered, though there are many legends about its origin. ... Coffee grown worldwide can trace its heritage back centuries to the ancient coffee forests on the Ethiopian plateau. There, legend says the goat herder Kaldi first discovered the ...", 42 | "cached_page_link": "http://webcache.googleusercontent.com/search?q=cache:lVL_jlE3B80J:www.ncausa.org/about-coffee/history-of-coffee+&cd=12&hl=en&ct=clnk&gl=us" 43 | }, 44 | { 45 | "position": 3, 46 | "title": "The Coffee House « Austin Improv Comedy Shows, Classes – The ...", 47 | "link": "http://www.hideouttheatre.com/coffeehouse", 48 | "displayed_link": "www.hideouttheatre.com/coffeehouse", 49 | "snippet": "The Hideout is downtown Austin's oldest independent coffee house. We are open morning to night, weekdays and weekends and serve beer, wine, fresh ...", 50 | "cached_page_link": "http://webcache.googleusercontent.com/search?q=cache:VD32HaeikQMJ:www.hideouttheatre.com/coffeehouse+&cd=13&hl=en&ct=clnk&gl=us", 51 | "related_pages_link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=related:www.hideouttheatre.com/coffeehouse+Coffee&tbo=1&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChAfMAJ6BAgJEAU", 52 | "rich_snippet": { 53 | "bottom": { 54 | "extensions": [ 55 | "Thu, Mar 21", 56 | "PGraph", 57 | "Thu, Mar 21", 58 | "Austin Secrets", 59 | "Thu, Mar 21", 60 | "The Free Fringe" 61 | ] 62 | } 63 | } 64 | }, 65 | { 66 | "position": 4, 67 | "title": "Home | The Coffee Bean & Tea Leaf", 68 | "link": "https://www.coffeebean.com/", 69 | "displayed_link": "https://www.coffeebean.com/", 70 | "snippet": "Never run out of your favorite coffees, teas and powders again with our auto-delivery subscription. Select how often your products arrive, pause and cancel ...", 71 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:WpQxSYo2c6AJ:https://www.coffeebean.com/+&cd=14&hl=en&ct=clnk&gl=us", 72 | "related_pages_link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=related:https://www.coffeebean.com/+Coffee&tbo=1&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChAfMAN6BAgFEAU" 73 | }, 74 | { 75 | "position": 5, 76 | "title": "Coffee Review - The World's Leading Coffee Guide", 77 | "link": "https://www.coffeereview.com/", 78 | "displayed_link": "https://www.coffeereview.com/", 79 | "snippet": "Coffee reviews, espresso ratings, informative articles, and coffee blogs by Kenneth Davids and other coffee experts.", 80 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:c9jncvPT1SsJ:https://www.coffeereview.com/+&cd=15&hl=en&ct=clnk&gl=us" 81 | }, 82 | { 83 | "position": 6, 84 | "title": "Coffee: Benefits, nutrition, and risks - Medical News Today", 85 | "link": "https://www.medicalnewstoday.com/articles/270202.php", 86 | "displayed_link": "https://www.medicalnewstoday.com/articles/270202.php", 87 | "date": "Dec 14, 2017", 88 | "snippet": "Drinking coffee may do much more than simply provide an energy boost when needed. Several scientific studies have identified a number of ...", 89 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:FB_36c_qnUAJ:https://www.medicalnewstoday.com/articles/270202.php+&cd=16&hl=en&ct=clnk&gl=us" 90 | }, 91 | { 92 | "position": 7, 93 | "title": "Peet's Coffee: The Original Craft Coffee", 94 | "link": "https://www.peets.com/", 95 | "displayed_link": "https://www.peets.com/", 96 | "snippet": "Since 1966, Peet's Coffee has offered superior coffees and teas by sourcing the best quality coffee beans and tea leaves in the world and adhering to strict ...", 97 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:BCjzno6zP6wJ:https://www.peets.com/+&cd=17&hl=en&ct=clnk&gl=us", 98 | "related_pages_link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=related:https://www.peets.com/+Coffee&tbo=1&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChAfMAZ6BAgGEAU" 99 | }, 100 | { 101 | "position": 8, 102 | "title": "The Best Coffee from Starbucks Coffee", 103 | "link": "https://www.starbucks.com/coffee", 104 | "displayed_link": "https://www.starbucks.com/coffee", 105 | "snippet": "Next stop: Asia / Pacific. Journey through the world's coffee regions with our Passport Series—coffee selected to give you a sense of place. Now we're visiting ...", 106 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:R7fHYjcarfsJ:https://www.starbucks.com/coffee+&cd=18&hl=en&ct=clnk&gl=us", 107 | "related_pages_link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=related:https://www.starbucks.com/coffee+Coffee&tbo=1&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChAfMAd6BAgHEAU" 108 | }, 109 | { 110 | "position": 9, 111 | "title": "Seventh Flag Coffee - Austin", 112 | "link": "http://www.seventhflagcoffee.com/", 113 | "displayed_link": "www.seventhflagcoffee.com/", 114 | "snippet": "Seventh Flag Coffee Co. is dedicated to creating the best overall coffee experience for every customer that walks through our doors. We are transforming the ...", 115 | "cached_page_link": "http://webcache.googleusercontent.com/search?q=cache:VZCbBEneWWkJ:www.seventhflagcoffee.com/+&cd=19&hl=en&ct=clnk&gl=us", 116 | "related_pages_link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=related:www.seventhflagcoffee.com/+Coffee&tbo=1&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChAfMAh6BAgIEAU" 117 | }, 118 | { 119 | "position": 10, 120 | "title": "Cosmic Coffee + Beer Garden - Austin, Tx", 121 | "link": "https://www.cosmiccoffeebeer.com/", 122 | "displayed_link": "https://www.cosmiccoffeebeer.com/", 123 | "snippet": "specialty coffee, craft cocktails, locally brewed beer, food trailers and live music in a permaculture inspired garden setting in south austin, tx.", 124 | "cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:tsOR-R1-gmQJ:https://www.cosmiccoffeebeer.com/+&cd=20&hl=en&ct=clnk&gl=us" 125 | } 126 | ], 127 | "related_searches": [ 128 | { 129 | "query": "mozarts coffee", 130 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=mozarts+coffee&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigAegQIChAB" 131 | }, 132 | { 133 | "query": "bennu coffee", 134 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=bennu+coffee&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigBegQIChAC" 135 | }, 136 | { 137 | "query": "coffee near me", 138 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=coffee+near+me&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigCegQIChAD" 139 | }, 140 | { 141 | "query": "coffee austin", 142 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=coffee+austin&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigDegQIChAE" 143 | }, 144 | { 145 | "query": "best coffee in austin", 146 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=best+coffee+in+austin&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigEegQIChAF" 147 | }, 148 | { 149 | "query": "coffee shops near me", 150 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=coffee+shops+near+me&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigFegQIChAG" 151 | }, 152 | { 153 | "query": "houndstooth coffee", 154 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=houndstooth+coffee&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigGegQIChAH" 155 | }, 156 | { 157 | "query": "epoch coffee", 158 | "link": "https://www.google.com/search?safe=active&gl=us&hl=en&q=epoch+coffee&sa=X&ved=2ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDVAigHegQIChAI" 159 | } 160 | ], 161 | "pagination": { 162 | "current": 2, 163 | "next": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=20&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDw0wMIjAE", 164 | "other_pages": { 165 | "1": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=0&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIeQ", 166 | "3": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=20&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIfA", 167 | "4": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=30&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIfg", 168 | "5": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=40&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIgAE", 169 | "6": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=50&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIggE", 170 | "7": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=60&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIhAE", 171 | "8": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=70&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIhgE", 172 | "9": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=80&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIiAE", 173 | "10": "https://www.google.com/search?q=Coffee&safe=active&gl=us&hl=en&ei=b5KRXOC6Bc3EswWc34_YDQ&start=90&sa=N&ved=0ahUKEwigrp6FxI_hAhVN4qwKHZzvA9s4ChDy0wMIigE" 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /src/test/java/serpapi/data/search_coffee_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "serp_api_data": { 3 | "internal_id": "5adc8e4cdefb2f3cd7482e07", 4 | "total_time_taken": 2.949662293, 5 | "google_url": "https://www.google.com/search?q=Coffee&oq=Coffee&uule=w+CAIQICIaQXVzdGluLFRleGFzLFVuaXRlZCBTdGF0ZXM&hl=en&gl=us&sourceid=chrome&ie=UTF-8" 6 | }, 7 | "search_information": { 8 | "total_results": 0, 9 | "time_taken": 0.8, 10 | "query": "Coffee", 11 | "location": "Austin, Texas" 12 | }, 13 | "local_map": { 14 | "link": "https://www.google.com/search?hl=en&gl=us&q=Coffee&npsic=0&rflfq=1&rldoc=1&rlha=0&rllag=30266836,-97744140,271&tbm=lcl&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQtgN6BAgAECo", 15 | "gps_coordinates": { 16 | "latitude": 30.266836, 17 | "longitude": -97.74414, 18 | "altitude": 271 19 | }, 20 | "image": "https://www.google.com/maps/vt/data=2NSTbSpojs16HQaUpxo6zcWYQcCrm7SksDnyP4aRZZlXkLZnu2oLnbmVgNS7JYL32QHrCEQgBIgWu6VGiBVi0iVfWhmkElU8FKfD_BMoYo_WTjjG7lZ8dfX3D9h2IAQqr9wzgTNPLiPmhXqr4SJMCavfKPn8Nm_p_XVgbkfFUdA-GhkIYCNAEfk7ZcWh6NSu2ufmeDJS0S8AR_ci2yGYLpGVmaYK1A4XtnzeZpv8874JMemT6pREUhvzmWbL-m_TTRq9dSeIdCK1lR2SVg20c8ZOx3ft9pmw0OLQVwkZsTM5FDm5-Cs09mhG42-e-Da8ZCAv39Tx39HPPuQTrvM1utXD9Aq3VSR8v-iID8sxqy_hCL5s7VWPIZUHxxtCzUZssFtan1idPh0WyXMcolf_Sav8T9nJw5jr9P-QdzQ7bQfTICkxnmpqm6sNq3VhbUYWMB5c7ez-JgaqMOAzShWbXWqya3uCejIaR4lXeqEGQeAtYHBNeFag_b4tovGDsiLdreaHLVM-llcGmlzU3FnX78ZX09zlCbyfZ1HBERw3lVwVatjGwtn2WlQsHl8e6yYtY7fQF80KDU1SyafHgS1v-gXEZC8o49ICyLEd1UXMOh_78mbLDADgB2ZLly9vp9ibHJFLraUY11qcRLOyDGQ7LJ82NbZybsZG7QNju2SacxloE7pIubl7ATTj9WxJBF6zEwT_bDQzfaHsGHpYV7PKmF-sA7SN3XjVK1rBJOODppq8lk1slyzZyHTeXnGhbmy1WQYWjAJDKBLJOCWJeB5z3XkW9G67N7jldaU_NOL3EBBqvfRMYhZcgt56tZoWa3PRiIKtQigZCVSUfSJPSfP2GG7fGfhNmBK_7cOK1OM-S0w449tXZE6_ZG0a6iN7AzYkQrZsMUIpz3Gvvwab39qnZ3zAnUVYYAcWnO_MU2CyTm2HxVh-cTh2POpkQNQ7TvzIpH7xQyVqXMZAvGAYE528s31ydQ_SHPLNznz-aEX0BYFAAK1FGyRrHWw9RQDcp-h9JFzxPBNlWF2-nHODMqjtXtwIxtoGI-GniAt9Vg4PlCgFjrKYDC-QOL_io0Hb5RA" 21 | }, 22 | "local_results": [ 23 | { 24 | "position": 1, 25 | "title": "Houndstooth Coffee", 26 | "reviews": 318, 27 | "price": "$", 28 | "type": "Coffee Shop", 29 | "address": "401 Congress Ave #100c", 30 | "description": "Cozy hangout for carefully sourced brews", 31 | "extensions": [ 32 | "Breakfast", 33 | "Outdoor seating", 34 | "Fast service" 35 | ], 36 | "thumbnail": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAqRXhpZgAASUkqAAgAAAABADEBAgAHAAAAGgAAAAAAAABHb29nbGUAAP/bAIQAAwICCA0NDgsKCw8NDg4LCAgKChALCAoQDwoKDg0NCgoLCw0ICw0LDQoICgoLCggKDQgNCgoICg0NCgoNDQoPCAEDBAQGBQYKBgYKEg4LDhEPDw8PEA8PEA8QEA8QEBAPDxAPDw8PDQ8PDQ8NDxANDRAPDw0PDQ4QDg0PDw8PDw4N/8AAEQgAWgBaAwERAAIRAQMRAf/EAB0AAAICAgMBAAAAAAAAAAAAAAUGBAcDCAECCQD/xABIEAACAQIDBQIKBQcLBQAAAAABAgMEEQASIQUGEyIxB0EIFCMyUWFxgZGhQlKywdEWM3JzkrGzCRckQ4KDoqPS4fA0VGN0wv/EABwBAAIDAQEBAQAAAAAAAAAAAAMEAgUGAQcACP/EADcRAAEDAgMGBAUDBAIDAAAAAAEAAhEDIQQSMQVBUWFxgZGx0fATIjKhwQYUQiNS4fFishUzQ//aAAwDAQACEQMRAD8A14XeiEa5j1A0SVupsNAh7zqegGpsATjyv4ecwPNelO2Hj6YzGnvAsWk3MCwM9eAubBcN2iUq3zOw7j5KcfPhfuwduEc6IjxHql3bMxjCc1Mj3x0Kxr2rbJbRpFPqMcx+RiOGhgcQy4B991VupboHiFGl2nu8/nGNepJCywfNUj++/oOG208YNJPcH1S5bFyFCO6ewZNY60J1P51CLevOgP8AiGGG1MUyzmT29EL5XDMNOKi1XYErfmK6F/QrEJ7iVkk+IX3Ybp4s/wAmHt7Cg+i5uoQHaPg6bVGqxLKPTHLG3ycxsfcCfVh9mMp75HUJFzUpbT7PKyO/Gp5UA6s0UgX9vLlPuJxYsrMd9Lh4oJFkPpqMHphsIJRmj2ITgoEoZKZdmbMt3YaYISzjKMAYNmQ4XOwNz62Pm8nKOXycsQlXlZXU2zLqHQWYEaXBuCwPijto4fNIaQeIMcjxsQV7vUdjK1L4VR8iQecgyL8jfqAUl7wbJq10bL8x9xxaYathnXbKji9q4rR7R2Ss0DC+eMvyuFAdRZyDlY3XUK2pQZc31rXvoaYabtcsjicaXwHM0M7iodPG31GHsB/+cEc124z75oDMTQJu2OkjyTbunsBHJL1CQDpaRpVOilswBp5LjQrYEsz5VCajMtUL8ggAmftBuYItaLTqmqRourReIB3G8iAM4Im5JkiAJCdd2KAkjJKhHrW5+KyKPliOGqwJLY7+qcxtH+yoD29CPJXHuPR1WYrw42AVWDCR1N2JGUhoct+W4Gfp872jVBssZiaZBVx9ntfxY1cpIrgspIkgKaAh0dFd2JvkZHPDy2Isc+jIw7arw4gZb2gX76298qw1Q1pbF+Mm3497kY2p2dUcv56nikPpaGJj7mK3HtBGHRgqP8QR0JH5hJ/GcEsV/g3bKbUQmM+lJZB8FZnQe5Rif7Nw+h57gH0KgcRxCWa7wXaf+qq2X9YiP7iytEB8PdgL/wBxSuWhw5emq6HsdyUQ+CTW900Xq0m/0fjhP/yLP7T9kb4asuroICB5NTooN1U6/D4HHgUNX7SFIuJzgHXcEmbz9n9I41iX4Ee7QjHW1n0zLDC47ZmErAipSb4R5KoNu9k9JfRCPY8nr6Zmb4W/2v8AD7SrDf8AYLNYn9LbNd/8yOjnfklY91ew+CSRYxIVzOqEkqbBiBc+THS/yxZVNp1IiyzlX9K4NgL257AmJB03aT91YrdgkaFoyQbFkv1uRp6cZ520Hh59U/R/S9GrTD2VCAQDds69x5Kqt9OzZqR+MmUgG1tVPNpY8p9N9cavZW0jiXCg7U6dhP4WS27+mHbOoOxoeCGxIggnMQ227U+C+ou1FgtgzRN9dUSXp0GVmWw9J1PoxvKVMtC8nr1c5Vudm3hAbOhiRauvjUqCHXxWaIEWOVUyxm73sWblXlcAMCpw4yoWhoI68vVIOZMkdufoou8nhw7FU5o55HUKRkjgZXZyRYgzRBMiqGzEtG2Z47K4zlWTiGg8kv8ADJCrfbXh2tKwjoaGSV2uFDyyMSf/AF4gVbTqotjjsWBoPEqPweJUHZPZPvrtDleA0NO7M8hlybOS7m7sVZRO6k3y5Ek5bXY+ca9+MMQD2CM34bTJutn9g+CrGkccf5QTjJHHHlSpp0QZFAtGpkYqgtZEJYqtgScVpzkzkPgVM1hwUCn2mDr7P+fDHh+i/cmWyPUu6E8kD1CLdEziQ5oxbIAzaGQMeU9ynEWh73QxsjXXxt/lU2I2rRw2KZhqmrogwd5IG4jXmqh3nAHXpf8AH14saCcrOmV23YppldWkieLVyokUpdUYqWFwdCRp3WKnoRiyr0TTDXG4IkELK7P2pR2gKgpgjKcpB15Gx0O7fYqwtm7ULtlW7OzAZVBY3YjWyg6c1ySAAL9cZt4cAXATqVf4vFUsJR+I/QQIGvDeRpqb6LHFQh6iFGQNd5VysBYkRuQpDadRoDa7WFr2w7hq3wCapMAa8vcqt/UVMVcA9jbzl/7A/hFKHd3ZsmYy0EJs6xnyNKrAtexIIU62OgJY/Utcjd7FxtbG0y+m4wOZ5+i/Pm1cJTwrgHAX5e+KfabwJ9l1cMr+KwKkYJIKTZi6jNZcsqqotYA6630xtMEc7ZqEm/p3WNxYcHfJayYt1P5PPYqsqmKC/DjluKOnJUkE2zSGQ3UgAOdSSDYYsP6YFm/cpINedXK49meC9s+JBZ5yDZSitToBp9URLp3dWPTTEMw3NA7Igo8yVH3T7CdmDis8bNZp8l5JFA4c00YvldQeWNLkgjQn2zdWMABRDACZTz/NFsX/ALdf8/8A14D8Q8VOGLxqg7QtoKRaZTr0MNN88sK4xDtl4R18n3PqvambW2kywxDvGfOSmfZ3b1tFUaJpkET5hIvCNjmGU3CsL3FgbC9hbTEGbLwrCS1pHdBr4/F1HCrVqEubEEgTYyN3FFNvbRlUDxjgRKeYM4qIxlBAuT42ALlkAY21dR1OgMNgcBXP9J0zz16WE24bk1V2/tGkCH1dP+LfvbiiO8XavIbNNPA5XiQqqiYtmADMgWKpJZlWzMArMq81rYsn4Gg5gaXfKBa40OhngeKpMNtOrhXONF0Fxv8AKDJA0iJkDd4rjZe+1TSzJVJTrmdFaNyZgGXlNwjO3q62PpAviqbhMM9vw6TjAP3PPf5cFeVcbi8c3+s4EW3RHDup+7W/kk1QrCmDy/0moiTjuA88UUksSE8JLZ5kRc3EUJmzNnAKOodkMbcOJuN3MTpw1jkrLFbdxRo5HFrAIAIGg0/kSBOmh14rZvcrYkzzyLIiJCyMQFWbMZcosWcyyKUYyFBGFQgxu2YApm0Oy8DSwOZtCYcCTObWZtIETJXlu1cZUxQBqOkg2+nTiYK2K3UmeOnmXhO+a7FkEdhdbWOaRDfS9gDoRjUYZvy671lazrmyJ0e8D57+KznyUIt/QksDnu3NWLo1wP7Gut7uFttUEHkpEu+DlcoopvpMDn2cBfXvFa3TvtqNPViOUypzbRIM+99RkINOQGarJbiwDU1EzEL5Y3sxykgEnLysQQWiLEe/eiVqEyuV3wqO6EW7vLw9P2sRGTj91EC2q8rtibMjdwjEJrZiVDHQ6qASozdyg9W01xj2sJMOJ7ar9EYh7G08zGNJ3Ett+Lc+CJ747qU0bpErrKrSSRu44aFFtEY3K5WuGaRyeZLrHoepBKuG+YBtQ6kGb2t0iZO4j81Dce80yXUWGw/iRBOad94gcDdMfaTuLTzIsktUzjhxxCFTBECHyswYGObPwwI5AwvrkvoAC7RwlPCUw2iQYiM1+R0jcd3FY6o2tiajjVBE65dOI1neNDvCTv5p425xLUXEgqMwnpyeKFyNJrQEXKed6cosBiQa9zYhkRH0nQbvq+3Fffs4dOd0kzqNTv8ApTRtXeNHtHLGQIuRLSJ0UBSP+lvflF7k695ULbO1cPUotDGu47o481uNlTmcMrYdBkgnyLUW7INvSQVsE8KKXTxxkViVBHi0wZSwjNiULBWsAHK5iq5ipMMHl93HQ+R6pvb9KgcE/KBMs/7DmtsuzzbEr1GVqV1y+MKspcWssjopyqcuWWILNGACESXISjLY37aIYJa8mdZ6z75WXkNSsan1NAjh0j3zW0e7dOwgkDC1yAOh0NvWfXi1w4hoVVVMkonTBM9ja5ggOW4uQTICQL39rW9/XDYO7moAIBtp2AKXvcOpPmnKOgLZhcgd/Vrekm55hMBoISrs/Y7SRxgAs1qlrgEnWVjfQaXLddNSPfDKM2Y7iUn8MOOY6rDT7qTWF4GvYX8lJ1/ZwCCOHj/hMtaI0C8mu2fZXBWrqIJ4X4c8mWMOxYK0xUKykIbgaEoWIIJH1sUTKGapfT3vW3rbcLcNkYPngCTEbgbc1O3q7MaijRONURPnpHq4FJmzMWVmiRi+rEHhDlzB75c17sXcThG5wW9+yp8Jtis2gWEyRpN9dZ39+GiVOz7Zta9ENoGTiiWSrpxSrx2cTU6BwygmQZGjJzwooyBUKAA2waph2logJKhtKqx5JIvx3eimbG7d6ALTsVOXNVeMIsnEd1upgOqxhCoaQZQASyqWXK9wiKLh/kelreSsHbRqEyCD753v36qw90u1TdpkfjvZzLoXWqRgvJbM/DjC3DO2UFlAVfpE2icIC0ZtehRBt3E03zTgQI3R4Ge65Pa/sGNg48UI8qlxW10rmOQSRtmgNBlQtC6kpxGtndeI1rntHDUmH5Q6eOnmlsVtjGYhhp1XtLTugbr3jp5drE7OfCW3dSakfNMI6ZamCOfhmXJTzPnlLgOc3EZEJlYOyKWynrdz4PywJNo3e5VOaznOuBEz75LY/fL+UE3deilGzNorxyYVhMlPVxjR0ErXkplQ5Ys+U8wLL0boYhwoty7+npZEZRNV2YxHUDzMqk9+fDTqZuHLFJQyNBNQOmQw8bRwWhSQbRLCJkz8VGiYyor2y6HAnVcQ354Bbe1p5DXf0TjKOHd/TMh1r7uZjL4X8Uu7P8JvbEm0aV6oS8OVJ2qFzFIVeCOQrGyr5Dnz+WcBRI7QkyOYSj/YKriQT8d4MRYCOvXl0X2Op0GgCi0jW5M23d1cO6vh4pA6QGhlzjjQ6zQxIUik5snFjiBZ0dSRduG/DGco2Z3nYhxkFm8nWJ6CB23c1Xtw7fqDtQBpMd5MR4kbpV60nhLzFQ3BC3Ctl8bpTa4vlvk1t0v32wVtRpaCW3980s+mWuIBkceP2XgZvVvFWyFjUtISWOfPmHOTc3GVdSVudO49+BAtJtEo7mPDQSDHOYVkdp+8bs7M0jjLTwZFDlQ1lFlPOLhTIOUZrBjy4sMTGYTw/CRoTlt7ukjZYcrThLg5KzUEqbNmHUdBfS/S5wu90UweqYpsL35QNVF2vui+QzIoyLZCBe91ULISLsdHDZnOQNYsFRSq4WDxMJp1B+TNFhbn78Ec2TsqrgBdJuEVa7qrENeMgcyFQdea6Nyt9Idc3DWDXBvHwU6WDqVKbqggAXg6noPJN+6naftSRZQtc8YAVeWOO8is9mUmOANlARXMTFg3LcWOCOcRG9LNaCCSYj7398VYu6+6NM9Qg2m1TUwFRrnkgCl3AcsqEMRlUnhqVKxkE3a4UwaDvS5cRuW3db2e7tAvV070/Hk4MHBZZJIYYIIoYFWkjSFzGZIEdXaSR73bmFwCCnhnRI14kT6Jg4oNsdOAMfeDv5IXNRQxgw7Pljgi8oI0iDx5BIJAcqEZHOdxIDIjc99BoMSdSe185gG8Le79V8zEUy2MpLuN/dlW/Yp2XbVpHd5at6ksgjR3hlkK6m7JJI5ILqStx1B77LYjTSZMuF9YQXOe4AZTb34J83+3DqKiNRMHKxTVFays0ZR0Y50VoS5tJCHkVZ04eaKZkeJssZjWxYo4oNZnIgiIH2M9UbDVauHLnBguLyfKL7lZVH2IbHZQx2fTAlVYjxam6kXI/N4qX0g1xCdZXqFoOY+K0RbscpK55GFS0hMhlfhrFZc7EAZs8nQm2lr26DFnRw1Kk7Mak9v9qeL2hUxFMU20oAjfwHQJ9m8Gyhku0glII5+eJblFfmPLmvanltqBdjYWIs4+phzDiTbh4Ktpiu2WhouIv4qXB4MezgAY4rZBIFZpqgmwUSMOVwCDn0B6HNp6Vn4qgRAaT1RGUK7TmzAdF0pfB+pwxFkF5AjeSzXJFyxvJqe43GvecK/vG2hiZ+A+8vN9UV2V2JUi5e66NIciQx6gkAeYfRc9NcCfj3gwAFJuEad5TtRdmNIHClWYZS2ruNbE/RK/DEP3dTORNlz9uzLMIj+RFGOkK+cRqM3cPTf1/HA6lepMZjr6KbKTI0TFSbDgGULGi6noiDv9Q9GOOeTElfBoEwEwUcfm+xfuwPcPe9T4o1TJovtXBgbBCOql7XpfJyfqpR/gwan9Teo/CC/6T0P5TDQwcq/or+7CtUuzutvKlTAyjoFoF4KUAHjADA2MR0N9DLofSLg3sbEG9wDcAokPidwumDGWeZV6xNZD7H+xtD8McJ+TuF0XcpDS6E+qoP8AkxYGNZRChrzc/wDfn7H4jASfypLpHUWyfqT82bEHm/cKYFkys/lB+h92JH/2H3uQh9Ci1FXp/aP2R92OVDfv+F1gsjVFU6j/AJ34K46e96GBqmCCo832D7sfTYdvNR4+9yIU1UOX3fuOJjQKJ1Klbe21GqOGdVLRyKt2C3JWwAv11toLnXphql9beo/CWq/QehUeLexLAcaLoB5lQfnm19o64A8nMdPuiNyZRr9lpf4LNBleoH/jp2PvlH44FQcXPPIBN1AA3urzY8nuk+xtL8MGg5B2Q5+Zd5G0Psqf4EWBDX3zRTohclTzH1TNr/d/d9+BHf3U/wDCitV6J+ot78z3xB+q60phqtsIsmZ2VRw9SzBQLL6SR6cTgmoY92QswDLpc/KmNtI80nM1siswOgHn2ye8sPbj6pSP+7blxlQbkboNo1BItFl9OeRV7+4IJPmRiQY0kS5RLnf2o271BteZEsPopcj3sxB/Y92HG02JYueo7wx6CSaVj1894xb2RiNSPSCDhlrW8EBxJ1KmQeKqCUVQSNWAF/eep+eJEFDX3GXAC1FlaweBlOxWYsSTwabUkn+uXC4AFd8ck7M0mytgm8z3P9jaeO/wHUL7+a4nOh/Rqf4EWIDU91PcEFrT5365v4QwI/Se/kpjUdlWfavtSVY1KOy+TXozL9M+gjDNEAm/uwS9UkBEOzqjR8ruoZrC7kBj0H0iCcVuLqOaSASO6ewrGuiQPBWpSDuHwxQkk6q6AAFgs2bFlQJSVYBSS2LFpKr3ALoxw1TJlKvAXWpQWw20pMhYA5wJEX//2Q==" 37 | }, 38 | { 39 | "position": 2, 40 | "title": "The Hideout Coffee House", 41 | "reviews": 123, 42 | "type": "Coffee Shop", 43 | "address": "617 Congress Ave", 44 | "description": "Area institution with a theater upstairs", 45 | "extensions": [ 46 | "Quick bite", 47 | "Fast service", 48 | "Great dessert" 49 | ], 50 | "thumbnail": "data:image/jpeg;base64,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" 51 | }, 52 | { 53 | "position": 3, 54 | "title": "Jo's Coffee", 55 | "reviews": 450, 56 | "price": "$$", 57 | "type": "Coffee Shop", 58 | "address": "242 W 2nd St", 59 | "description": "Coffee outpost with snacks & a patio", 60 | "extensions": [ 61 | "Comfort food", 62 | "Quick bite", 63 | "Fast service" 64 | ], 65 | "thumbnail": "data:image/jpeg;base64,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" 66 | } 67 | ], 68 | "organic_results": [ 69 | { 70 | "position": 1, 71 | "title": "All About Coffee - Page 522 - Google Books Result", 72 | "link": "https://books.google.com/books?id=4O_RAAAAMAAJ&pg=PA522&lpg=PA522&dq=Coffee&source=bl&ots=QfeEK8jWl_&sig=SWDfj277-YbtgrWpG7IIPCLq5HA&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwCHoECAAQdw", 73 | "snippet": "Although little thinking at the time that their greatest success was to be achieved in coffee, and that a new idea of one of the partners — that of marketing roasted coffee in original packages — would make their name familiar in every hamlet in the country, yet the first two entries in the original day-book of McDonald ...", 74 | "rich_snippet": { 75 | "top": { 76 | "extensions": [ 77 | "William Harrison Ukers - 1922", 78 | "Coffee" 79 | ] 80 | } 81 | } 82 | }, 83 | { 84 | "position": 2, 85 | "title": "Coffee: A Dark History - Page 70 - Google Books Result", 86 | "link": "https://books.google.com/books?id=82ourCcpnpwC&pg=PA70&lpg=PA70&dq=Coffee&source=bl&ots=K_Q_n5i5ct&sig=FqCLQ1vxqroNbVDkccmmGRBi_gM&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwCXoECAAQeg", 87 | "snippet": "the demand for coffee was met by Ethiopia entirely. Evidence of coffee exports from Zeila, near Djibouti on the western Red Sea coast, can be found in reports of the jurist Ibn Hadjar al-Haytami of Mecca in the late fifteenth century as well as in an account of a boat captured by the Portuguese in 1542 on the way to Shihr in ...", 88 | "rich_snippet": { 89 | "top": { 90 | "extensions": [ 91 | "Antony Wild - 2005", 92 | "Cooking" 93 | ] 94 | } 95 | } 96 | }, 97 | { 98 | "position": 3, 99 | "title": "Coffee Boom, Government Expenditure, and Agricultural Prices: The ...", 100 | "link": "https://books.google.com/books?id=HN_dAbD-IQUC&pg=PA32&lpg=PA32&dq=Coffee&source=bl&ots=gN30G0MHdv&sig=OFlzmJKMEpP9EXcGZL5irDXdHgg&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwCnoECAAQfQ", 101 | "snippet": "Ts + PCC = Td. (8) Effects of Changes in Coffee Price From the above set of equations, what happens to the real exchange rate when the price of coffee goes up can be examined. With the level of resources for the economy held constant, the market for nontraded goods is looked at first. On the supply side, an increase in ...", 102 | "rich_snippet": { 103 | "top": { 104 | "extensions": [ 105 | "Jorge García García, ‎Gabriel Montes Llamas - 1988", 106 | "Business & Economics" 107 | ] 108 | } 109 | } 110 | }, 111 | { 112 | "position": 4, 113 | "title": "Liberian Coffee in Ceylon: The History of the Introduction and ...", 114 | "link": "https://books.google.com/books?id=YFZJAAAAYAAJ&pg=PA16-IA4&lpg=PA16-IA4&dq=Coffee&source=bl&ots=eTuoXacF3Z&sig=1EbMZ0rO5ry6MKpqZiMwdl9wNRQ&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwC3oFCAAQgQE", 115 | "snippet": "Liberian coffee — its future in Ceylon ... 72 Liberian coffee : hybrids, shade, &c... ... 74 Shade for coffee, Aranian and Liberian... ... 76 Rapid germination of dry Liberian coffee seeds by means of pulp water... ... ... 77 Liberian and Mocha coffee on our eastern ranges 77 The Liberian coffee plant in India ... ... 78 Liberian coffee.", 116 | "rich_snippet": { 117 | "top": { 118 | "extensions": [ 119 | "G. A. Cruwell - 1878", 120 | "Coffee" 121 | ] 122 | } 123 | } 124 | }, 125 | { 126 | "position": 5, 127 | "title": "Fair Trade Coffee: The Prospects and Pitfalls of Market-driven ...", 128 | "link": "https://books.google.com/books?id=TBrCVj_LTq0C&pg=PA118&lpg=PA118&dq=Coffee&source=bl&ots=iRsOxYg8dQ&sig=TTIy4_1TYoBb4TwBYEemd9HZtyE&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwDHoFCAAQhAE", 129 | "snippet": "the boom, prices are artificially high as new trees mature; and during the bust, planters are unable to easily switch to other commodities due to relatively large amounts of fixed capital they have invested in coffee. According to Talbot (2004: 35): 'In the absence of any intervention in the market, [the nature of coffee cultivation] ...", 130 | "rich_snippet": { 131 | "top": { 132 | "extensions": [ 133 | "Gavin Fridell - 2007", 134 | "Political Science" 135 | ] 136 | } 137 | } 138 | }, 139 | { 140 | "position": 6, 141 | "title": "The Art and Craft of Coffee: An Enthusiast's Guide to Selecting, ...", 142 | "link": "https://books.google.com/books?id=2nZsiYAftV4C&pg=PA144&lpg=PA144&dq=Coffee&source=bl&ots=mVEWT9pOET&sig=z6ivz5Di88awuTdHaWMfVa6F09Y&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwDXoFCAAQhwE", 143 | "snippet": "Brewed coffee is a volatile liquid; it cools and evaporates quickly. For best results, drink coffee immediately after brewing. If you can't drink it right away, place it in a vessel that keeps it hot and preserves as much flavor as possible. Because coffee flavor deteriorates quickly, give storage some thought so you can enjoy more ...", 144 | "rich_snippet": { 145 | "top": { 146 | "extensions": [ 147 | "Kevin Sinnott - 2011", 148 | "Cooking" 149 | ] 150 | } 151 | } 152 | }, 153 | { 154 | "position": 7, 155 | "title": "The History of Coffee in Guatemala - Page 110 - Google Books Result", 156 | "link": "https://books.google.com/books?id=trfs8E0EdbUC&pg=PA110&lpg=PA110&dq=Coffee&source=bl&ots=fMXVqjRCUP&sig=TOhkunMUnTTetVxWgXn3tjgK7vQ&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwDnoFCAAQigE", 157 | "snippet": "Beginning in 1818, Le Havre, France had a market both for coffee bought for immediate delivery and for coffee bought pending delivery; New York was the next to establish such a system. Hamburg, the European port importing the most coffee shipped overseas, created the futures system (also known as the futures ...", 158 | "rich_snippet": { 159 | "top": { 160 | "extensions": [ 161 | "Regina Wagner, ‎Cristóbal von Rothkirch, ‎Eric Stull - 2001", 162 | "Cooking" 163 | ] 164 | } 165 | } 166 | }, 167 | { 168 | "position": 8, 169 | "title": "Coffee; from Plantation to Cup: A Brief History of Coffee Production ...", 170 | "link": "https://books.google.com/books?id=YcJBAAAAIAAJ&pg=PA148&lpg=PA148&dq=Coffee&source=bl&ots=BWx7TLzZLx&sig=aHHre22Q_gVMtQIBJzBgu5ya1As&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwD3oFCAAQjwE", 171 | "snippet": "A well-developed tree yields from one-half to one pound of coffee. Laguayra coffee is grown on highlands in the province of Valencia, some six or eight miles from the capital city of Caracas, in Venezuela. A coast range of mountains extends through the northwestern part of the province of Caracas. The bulk of the crop is ...", 172 | "rich_snippet": { 173 | "top": { 174 | "extensions": [ 175 | "Francis Beatty Thurber - 1881", 176 | "Coffee" 177 | ] 178 | } 179 | } 180 | }, 181 | { 182 | "position": 9, 183 | "title": "A Review of Literature of COFFEE RESEARCH in Indonesia", 184 | "link": "https://books.google.com/books?id=VZYOAQAAIAAJ&pg=PA2&lpg=PA2&dq=Coffee&source=bl&ots=ndrxnv2P5g&sig=swM2cTlH07OvUCfUQc6gFm3sD2w&hl=en&sa=X&ved=2ahUKEwiAqe_S_s3aAhUms1QKHYlJAYYQ6AEwEHoFCAAQkgE", 185 | "snippet": "A few other books published as coffee planters manuals may have some value, but they are wholly obsolete and of little historical interest. For example, in Dutch are the manuals by de Munnick (1843 and 1863), a report by van Spall on Ceylon (1861), Steinmetz (1865), the Handle- iding of 1873, de Sturler (no date), and ..." 186 | } 187 | ] 188 | } 189 | --------------------------------------------------------------------------------