|
- """
- :synopsis: Main crawler module, to oversee all site-specific crawlers.
-
- Contains all website/framework-specific Class crawlers.
- """
-
- import requests, time, threading
-
- from bitshift.crawler import indexer
-
- from ..codelet import Codelet
- from ..database import Database
-
- class GitHubCrawler(threading.Thread):
- """
- Crawler that retrieves links to all of GitHub's public repositories.
-
- GitHubCrawler is a threaded singleton that queries GitHub's API for urls
- to its public repositories, which it inserts into a :class:`Queue.Queue`
- shared with :class:`indexer.GitIndexer`.
-
- :ivar clone_queue: (:class:`Queue.Queue`) Contains :class:`GitRepository`
- with repository metadata retrieved by :class:`GitHubCrawler`, and other Git
- crawlers, to be processed by :class:`indexer.GitIndexer`.
- """
-
- AUTHENTICATION = {
- "client_id" : "436cb884ae09be7f2a4e",
- "client_secret" : "8deeefbc2439409c5b7a092fd086772fe8b1f24e"
- }
-
- def __init__(self, clone_queue):
- """
- Create an instance of the singleton `GitHubCrawler`.
-
- :param clone_queue: see :attr:`self.clone_queue`
-
- :type clone_queue: see :attr:`self.clone_queue`
- """
-
- self.clone_queue = clone_queue
- super(GitHubCrawler, self).__init__(name=self.__class__.__name__)
-
- def run(self):
- """
- Query the GitHub API for data about every public repository.
-
- Pull all of GitHub's repositories by making calls to its API in a loop,
- accessing a subsequent page of results via the "next" URL returned in an
- API response header. Uses Severyn Kozak's (sevko) authentication
- credentials. For every new repository, a :class:`GitRepository` is
- inserted into :attr:`self.clone_queue`.
- """
-
- next_api_url = "https://api.github.com/repositories"
- api_request_interval = 5e3 / 60 ** 2
-
- while len(next_api_url) > 0:
- start_time = time.time()
-
- try:
- response = requests.get(next_api_url,
- params=self.AUTHENTICATION)
- except ConnectionError as exception:
- continue
-
- queue_percent_full = (float(self.clone_queue.qsize()) /
- self.clone_queue.maxsize) * 100
-
- for repo in response.json():
- while self.clone_queue.full():
- time.sleep(1)
-
- self.clone_queue.put(indexer.GitRepository(
- repo["html_url"], repo["full_name"].replace("/", ""),
- "GitHub"))
-
- if int(response.headers["x-ratelimit-remaining"]) == 0:
- time.sleep(int(response.headers["x-ratelimit-reset"]) -
- time.time())
-
- next_api_url = response.headers["link"].split(">")[0][1:]
- with open(".github_api.log", "w") as log_file:
- log_file.write("%s\n" % next_api_url)
-
- sleep_time = api_request_interval - (time.time() - start_time)
- if sleep_time > 0:
- time.sleep(sleep_time)
-
- def _get_repo_stars(self, repo_name):
- """
- Return the number of stargazers for a repository.
-
- Queries the GitHub API for the number of stargazers for a given
- repository, and blocks if the query limit is exceeded.
-
- :param repo_name: The name of the repository, in
- `username/repository_name` format.
-
- :type repo_name: str
-
- :return: The number of stargazers for the repository.
- :rtype: int
- """
-
- API_URL = "https://api.github.com/search/repositories"
-
-
- params = self.AUTHENTICATION
- params["q"] = "repo:%s" % repo_name
-
- resp = requests.get(API_URL,
- params=params,
- headers={
- "Accept" : "application/vnd.github.preview"
- })
-
- if int(resp.headers["x-ratelimit-remaining"]) == 0:
- time.sleep(int(resp.headers["x-ratelimit-reset"]) - time.time())
-
- return int(resp.json()["items"][0]["stargazers_count"])
-
- class BitbucketCrawler(threading.Thread):
- """
- Crawler that retrieves links to all of Bitbucket's public repositories.
-
- BitbucketCrawler is a threaded singleton that queries Bitbucket's API for
- urls to its public repositories, and inserts them as
- :class:`indexer.GitRepository` into a :class:`Queue.Queue` shared with
- :class:`indexer.GitIndexer`.
-
- :ivar clone_queue: (:class:`Queue.Queue`) The shared queue to insert
- :class:`indexer.GitRepository` repository urls into.
- """
-
- def __init__(self, clone_queue):
- """
- Create an instance of the singleton `BitbucketCrawler`.
-
- :param clone_queue: see :attr:`self.clone_queue`
-
- :type clone_queue: see :attr:`self.clone_queue`
- """
-
- self.clone_queue = clone_queue
- super(BitbucketCrawler, self).__init__(name=self.__class__.__name__)
-
- def run(self):
- """
- Query the Bitbucket API for data about every public repository.
-
- Query the Bitbucket API's "/repositories" endpoint and read its
- paginated responses in a loop; any "git" repositories have their
- clone-urls and names inserted into a :class:`indexer.GitRepository` in
- :attr:`self.clone_queue`.
- """
-
- next_api_url = "https://api.bitbucket.org/2.0/repositories"
-
- while True:
- try:
- response = requests.get(next_api_url).json()
- except ConnectionError as exception:
- time.sleep(0.5)
- continue
-
- queue_percent_full = (float(self.clone_queue.qsize()) /
- self.clone_queue.maxsize) * 100
-
- for repo in response["values"]:
- if repo["scm"] == "git":
- while self.clone_queue.full():
- time.sleep(1)
-
- clone_links = repo["links"]["clone"]
- clone_url = (clone_links[0]["href"] if
- clone_links[0]["name"] == "https" else
- clone_links[1]["href"])
- links.append("clone_url")
- self.clone_queue.put(indexer.GitRepository(
- clone_url, repo["full_name"], "Bitbucket"))
-
- next_api_url = response["next"]
- with open(".bitbucket_api.log", "w") as log_file:
- log_file.write("%s\n" % next_api_url)
-
- time.sleep(0.2)
|