[docs]classDataForSeoAPIWrapper(BaseModel):"""Wrapper around the DataForSeo API."""model_config=ConfigDict(arbitrary_types_allowed=True,extra="forbid",)default_params:dict=Field(default={"location_name":"United States","language_code":"en","depth":10,"se_name":"google","se_type":"organic",})"""Default parameters to use for the DataForSEO SERP API."""params:dict=Field(default={})"""Additional parameters to pass to the DataForSEO SERP API."""api_login:Optional[str]=None"""The API login to use for the DataForSEO SERP API."""api_password:Optional[str]=None"""The API password to use for the DataForSEO SERP API."""json_result_types:Optional[list]=None"""The JSON result types."""json_result_fields:Optional[list]=None"""The JSON result fields."""top_count:Optional[int]=None"""The number of top results to return."""aiosession:Optional[aiohttp.ClientSession]=None"""The aiohttp session to use for the DataForSEO SERP API."""@model_validator(mode="before")@classmethoddefvalidate_environment(cls,values:Dict)->Any:"""Validate that login and password exists in environment."""login=get_from_dict_or_env(values,"api_login","DATAFORSEO_LOGIN")password=get_from_dict_or_env(values,"api_password","DATAFORSEO_PASSWORD")values["api_login"]=loginvalues["api_password"]=passwordreturnvalues
[docs]asyncdefarun(self,url:str)->str:"""Run request to DataForSEO SERP API and parse result async."""returnself._process_response(awaitself._aresponse_json(url))
[docs]defrun(self,url:str)->str:"""Run request to DataForSEO SERP API and parse result async."""returnself._process_response(self._response_json(url))
def_prepare_request(self,keyword:str)->dict:"""Prepare the request details for the DataForSEO SERP API."""ifself.api_loginisNoneorself.api_passwordisNone:raiseValueError("api_login or api_password is not provided")cred=base64.b64encode(f"{self.api_login}:{self.api_password}".encode("utf-8")).decode("utf-8")headers={"Authorization":f"Basic {cred}","Content-Type":"application/json"}obj={"keyword":quote(keyword)}obj={**obj,**self.default_params,**self.params}data=[obj]_url=(f"https://api.dataforseo.com/v3/serp/{obj['se_name']}"f"/{obj['se_type']}/live/advanced")return{"url":_url,"headers":headers,"data":data,}def_check_response(self,response:dict)->dict:"""Check the response from the DataForSEO SERP API for errors."""ifresponse.get("status_code")!=20000:raiseValueError(f"Got error from DataForSEO SERP API: {response.get('status_message')}")returnresponsedef_response_json(self,url:str)->dict:"""Use requests to run request to DataForSEO SERP API and return results."""request_details=self._prepare_request(url)response=requests.post(request_details["url"],headers=request_details["headers"],json=request_details["data"],)response.raise_for_status()returnself._check_response(response.json())asyncdef_aresponse_json(self,url:str)->dict:"""Use aiohttp to request DataForSEO SERP API and return results async."""request_details=self._prepare_request(url)ifnotself.aiosession:asyncwithaiohttp.ClientSession()assession:asyncwithsession.post(request_details["url"],headers=request_details["headers"],json=request_details["data"],)asresponse:res=awaitresponse.json()else:asyncwithself.aiosession.post(request_details["url"],headers=request_details["headers"],json=request_details["data"],)asresponse:res=awaitresponse.json()returnself._check_response(res)def_filter_results(self,res:dict)->list:output=[]types=self.json_result_typesifself.json_result_typesisnotNoneelse[]fortaskinres.get("tasks",[]):forresultintask.get("result",[]):foriteminresult.get("items",[]):iflen(types)==0oritem.get("type","")intypes:self._cleanup_unnecessary_items(item)iflen(item)!=0:output.append(item)ifself.top_countisnotNoneandlen(output)>=self.top_count:breakreturnoutputdef_cleanup_unnecessary_items(self,d:dict)->dict:fields=self.json_result_fieldsifself.json_result_fieldsisnotNoneelse[]iflen(fields)>0:fork,vinlist(d.items()):ifisinstance(v,dict):self._cleanup_unnecessary_items(v)iflen(v)==0:deld[k]elifknotinfields:deld[k]if"xpath"ind:deld["xpath"]if"position"ind:deld["position"]if"rectangle"ind:deld["rectangle"]fork,vinlist(d.items()):ifisinstance(v,dict):self._cleanup_unnecessary_items(v)returnddef_process_response(self,res:dict)->str:"""Process response from DataForSEO SERP API."""toret="No good search result found"fortaskinres.get("tasks",[]):forresultintask.get("result",[]):item_types=result.get("item_types")items=result.get("items",[])if"answer_box"initem_types:toret=next(itemforiteminitemsifitem.get("type")=="answer_box").get("text")elif"knowledge_graph"initem_types:toret=next(itemforiteminitemsifitem.get("type")=="knowledge_graph").get("description")elif"featured_snippet"initem_types:toret=next(itemforiteminitemsifitem.get("type")=="featured_snippet").get("description")elif"shopping"initem_types:toret=next(itemforiteminitemsifitem.get("type")=="shopping").get("price")elif"organic"initem_types:toret=next(itemforiteminitemsifitem.get("type")=="organic").get("description")iftoret:breakreturntoret