网络抓取Google搜索结果

时间:2020-03-04 21:59:33

标签: python web-scraping beautifulsoup web-crawler

我正在逐页抓取Google Scholar搜索结果。一定数量的页面后,弹出的验证码会中断我的代码。我了解到Google限制了我每小时可以发出的请求。有没有办法解决这个限制?我读了一些有关API的内容,但不确定是否有帮助。

2 个答案:

答案 0 :(得分:8)

自从我过去从Google抓取以来,我感到您很痛苦。为了完成工作,我尝试了以下方法。此列表按从最简单到最困难的技术排序。

  • 每秒限制您的请求:Google和许多其他网站每秒将识别来自同一台计算机的大量请求,并自动将其阻止,作为对Denial-of-Service attacks的防御措施。您需要做的就是保持柔和,例如,每1-5秒仅执行一次请求,以避免被迅速禁止。
  • 随机化睡眠时间:将代码精确睡眠1秒钟很容易被检测为脚本。使其在每次迭代中随机睡眠一段时间。 This StackOverflow answer显示了有关如何将其随机化的示例。
  • 使用启用了Cookie的网络抓取工具库:如果您从头开始编写抓取代码,Google会注意到您的请求未返回收到的Cookie。使用良好的库,例如Scrapy来解决此问题。
  • 使用多个IP地址:节流肯定会降低您的抓取吞吐量。如果确实需要快速抓取数据,则需要使用多个IP地址,以避免被禁止。有几家公司在Internet上以一定的价格提供这种服务。我使用过ProxyMesh,非常喜欢它们的质量,文档和客户支持。
  • 使用真实的浏览器:如果某些刮板不处理JavaScript或具有图形界面,则某些网站会识别您的刮板。例如,将真实的浏览器与Selenium配合使用将解决此问题。

您还可以查看为纽约大学Web搜索引擎课程编写的my crawler project。它不会抓取Google 本身,但包含一些上述技术,例如节流和随机化睡眠时间。

答案 1 :(得分:2)

根据个人经验,抓取Google Scholar。 45秒足以避免验证码和漫游器检测。我的刮板运行了3天以上,没有被发现。如果确实遭到举报,则等待大约2个小时就足以重新开始。 Here is an extract from my code.

class ScholarScrape():
    def __init__(self):
        self.page = None
        self.last_url = None
        self.last_time = time.time()
        self.min_time_between_scrape = int(ConfigFile.instance().config.get('scholar','bot_avoidance_time'))
        self.header = {'User-Agent':ConfigFile.instance().config.get('scholar','user_agent')}
        self.session = requests.Session()
        pass

    def search(self, query=None, year_lo=None, year_hi=None, title_only=False, publication_string=None, author_string=None, include_citations=True, include_patents=True):
        url = self.get_url(query, year_lo, year_hi, title_only, publication_string, author_string, include_citations, include_patents)
        while True:
            wait_time = self.min_time_between_scrape - (time.time() - self.last_time)
            if wait_time > 0:
                logger.info("Delaying search by {} seconds to avoid bot detection.".format(wait_time))
                time.sleep(wait_time)
            self.last_time = time.time()
            logger.info("SCHOLARSCRAPE: " + url)
            self.page = BeautifulSoup(self.session.get(url, headers=self.header).text, 'html.parser')
            self.last_url = url

            if "Our systems have detected unusual traffic from your computer network" in str(self.page):
                raise BotDetectionException("Google has blocked this computer for a short time because it has detected this scraping script.")

            return

    def get_url(self, query=None, year_lo=None, year_hi=None, title_only=False, publication_string=None, author_string=None, include_citations=True, include_patents=True):
        base_url = "https://scholar.google.com.au/scholar?"
        url = base_url + "as_q=" + urllib.parse.quote(query)

        if year_lo is not None and bool(re.match(r'.*([1-3][0-9]{3})', str(year_lo))):
            url += "&as_ylo=" + str(year_lo)

        if year_hi is not None and bool(re.match(r'.*([1-3][0-9]{3})', str(year_hi))):
            url += "&as_yhi=" + str(year_hi)

        if title_only:
            url += "&as_yhi=title"
        else:
            url += "&as_yhi=any"

        if publication_string is not None:
            url += "&as_publication=" + urllib.parse.quote('"' + str(publication_string) + '"')

        if author_string is not None:
            url += "&as_sauthors=" + urllib.parse.quote('"' + str(author_string) + '"')

        if include_citations:
            url += "&as_vis=0"
        else:
            url += "&as_vis=1"

        if include_patents:
            url += "&as_sdt=0"
        else:
            url += "&as_sdt=1"

        return url

    def get_results_count(self):
        e = self.page.findAll("div", {"class": "gs_ab_mdw"})
        try:
            item = e[1].text.strip()
        except IndexError as ex:
            if "Our systems have detected unusual traffic from your computer network" in str(self.page):
                raise BotDetectionException("Google has blocked this computer for a short time because it has detected this scraping script.")
            else:
                raise ex

        if self.has_numbers(item):
            return self.get_results_count_from_soup_string(item)
        for item in e:
            item = item.text.strip()
            if self.has_numbers(item):
                return self.get_results_count_from_soup_string(item)
        return 0

    @staticmethod
    def get_results_count_from_soup_string(element):
        if "About" in element:
            num = element.split(" ")[1].strip().replace(",","")
        else:
            num = element.split(" ")[0].strip().replace(",","")
        return num

    @staticmethod
    def has_numbers(input_string):
        return any(char.isdigit() for char in input_string)


class BotDetectionException(Exception):
    pass

if __name__ == "__main__":
    s = ScholarScrape()
    s.search(**{
        "query":"\"policy shaping\"",
        # "publication_string":"JMLR",
        "author_string": "gilboa",
        "year_lo": "1995",
        "year_hi": "2005",

    })
    x = s.get_results_count()
    print(x)