레이블이 BeautifulSoup인 게시물을 표시합니다. 모든 게시물 표시
레이블이 BeautifulSoup인 게시물을 표시합니다. 모든 게시물 표시

2022년 10월 23일 일요일

python selenium 실전 사용과 BeautifulSoup로 정보 읽기

selenium 과 BeautifulSoup 그리고 크롬을 이용한 예제


web page의 원하는 부분을 읽어낼때는 크롬의 F12를 눌러 DevTools 를 이용합니다.

여기에서는 daum page의 "로또당첨번호" 라는 텍스트 위치를 읽어 내보도록 하겠습니다.


DevTools에서 마우스를 클릭해가면서 원하는 부분을 찾습니다.
그리고 마우스 우측 버튼을 눌러 Copy > Copy selector를 합니다.
여기에서는 아래와 같은 값이 되며 해당값은 CSS selector로 BeautifulSoup 에서 원하는 위치를 읽어낼때 사용하게 될것입니다.

#wrapSearch > div.slide_favorsch > ul:nth-child(2) > li:nth-child(1) > a

해당 부분이 왜 저렇게 되는지는 css selector 를 검색 해보시기 바랍니다.


selenium을 이용해 html 읽기

selenium 을 통해서 html을 읽는 예제는 아래와 같이 작업하였습니다.
아래 파일은 selenium_v1.py 로 저장하겠습니다.

import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import os

class selenium_v1:
	def __init__(self):
		self.driver = None
		self.download_path = None
		return
		
	def create_web_driver_chrome(self, headless=True, download_path=None):
		options = webdriver.ChromeOptions()
		options.add_argument('disable-gpu')
		options.add_experimental_option('excludeSwitches',['enable-logging'])
		
		if headless:
			options.add_argument('headless')
		
		if download_path!=None:
			self.download_path = os.path.abspath(download_path)
			prefs = {"download.default_directory":self.download_path}
			options.add_experimental_option("prefs",prefs)
		
		if self.driver!=None:
			self.driver.close()
		self.driver = None
		
		chromedriver_autoinstaller.install()
		
		try:
			self.driver = webdriver.Chrome(options=options)
			self.driver.implicitly_wait(10)
		except Exception as e:
			print("exception",e)
		
		return self.driver
		
	def download_wait(self, timeout_min=1):
		if self.download_path==None:
			print("error can not find download path")
			return -2
		path_to_downloads = self.download_path
		seconds = 0
		dl_wait = True
		sum_after = 0
		while dl_wait and seconds < timeout_min*60:
			time.sleep(5)
			dl_wait = False
			sum_before = sum_after
			sum_after = 0
			for fname in os.listdir(path_to_downloads):
				if fname.endswith('.crdownload'):
					sum_after += os.stat(path_to_downloads+'/'+fname).st_size
					dl_wait = True
			if dl_wait and seconds > 10 and sum_before == sum_after:
				print("download timeout")
				dl_wait = False
				return -1
			seconds += 5
		return seconds
	
	def get(self,url):
		if self.driver == None:
			return -1
		return self.driver.get(url)
		
	def close(self):
		if self.driver == None:
			return -1
		return self.driver.close()
		
	def save_page_source(self, filename):
		if self.driver == None:
			return -1
		html = self.driver.page_source
		try:
			f = open(filename, 'w', encoding = 'utf-8')
			f.write(html)
			f.close()
		except:
			print("exception",e)
		return 0
		
if __name__ == "__main__":
	sel = selenium_v1()
	sel.create_web_driver_chrome(headless=True,download_path=".")
	print(sel.get("https://www.daum.net"))
	print(sel.driver.page_source)
	print(sel.get("https://www.python.org/ftp/python/3.9.11/python-3.9.11-embed-amd64.zip"))
	sel.download_wait()
	print(sel.driver.page_source)
	sel.save_page_source("test.html")

여기 예제는 chrome 드라이버를 이용한 파일을 다운로드 하는 코드를 샘플로 작성하였고 실제 page는 sel.driver.page_source 가 됩니다.


import 에러 발생시

ModuleNotFoundError: No module named 'chromedriver_autoinstaller'

pip install chromedriver-autoinstaller


ModuleNotFoundError: No module named 'selenium'

pip install -U selenium


sel.driver.page_source 여기에 html 코드가 들어가는데는 시간이 좀 필요합니다. 그래서 조금 시간이 필요한데 driver.implicitly_wait(3) 함수를 이용해서 html 페이지가 로딩되도록 하겠습니다.


daum 페이지를 읽어 화면에 출력하는 코드

import selenium_v1


if __name__ == "__main__":
	sel = selenium_v1.selenium_v1()
	sel.create_web_driver_chrome(headless=True, download_path=".")
	print(sel.get("https://www.daum.net"))
	sel.driver.implicitly_wait(3)
	print(sel.driver.page_source)


BeautifulSoup 로 정보 읽기

import selenium_v1
from bs4 import BeautifulSoup


if __name__ == "__main__":
	sel = selenium_v1.selenium_v1()
	sel.create_web_driver_chrome(headless=True, download_path=".")
	# get page
	sel.get("https://www.daum.net")
	sel.driver.implicitly_wait(3)
	#print(sel.driver.page_source)

	soup = BeautifulSoup(sel.driver.page_source, "html.parser")
	result = soup.select('#wrapSearch > div.slide_favorsch > ul:nth-child(2) > li:nth-child(1) > a')
	print(f'type:{type(result)},result:{result}')
	for one in result:
		print(f'type:{type(one)},result:{one},href:{one.href},text:{one.text},get:{one.get("data-tiara-layer")}')

	result = soup.select_one('#wrapSearch > div.slide_favorsch > ul:nth-child(2) > li:nth-child(1) > a')
	print(f'type:{type(result)},result:{result}')

실행 결과

type:<class 'bs4.element.ResultSet'>,result:[<a class="link_favorsch" data-tiara-action-name="header-search-txt" data-tiara-layer="header search txt" href="https://search.daum.net/search?w=tot&amp;q=%EB%A1%9C%EB%98%90%EB%8B%B9%EC%B2%A8%EB%B2%88%ED%98%B8&amp;DA=NPI&amp;rtmaxcoll=LOT">로또당첨번호</a>]
type:<class 'bs4.element.Tag'>,result:<a class="link_favorsch" data-tiara-action-name="header-search-txt" data-tiara-layer="header search txt" href="https://search.daum.net/search?w=tot&amp;q=%EB%A1%9C%EB%98%90%EB%8B%B9%EC%B2%A8%EB%B2%88%ED%98%B8&amp;DA=NPI&amp;rtmaxcoll=LOT">로또당첨번호</a>,href:None,text:로또당첨번호,get:header search txt
type:<class 'bs4.element.Tag'>,result:<a class="link_favorsch" data-tiara-action-name="header-search-txt" data-tiara-layer="header search txt" href="https://search.daum.net/search?w=tot&amp;q=%EB%A1%9C%EB%98%90%EB%8B%B9%EC%B2%A8%EB%B2%88%ED%98%B8&amp;DA=NPI&amp;rtmaxcoll=LOT">로또당첨번호</a>


import 에러시

ModuleNotFoundError: No module named 'bs4'

pip install beautifulsoup4


BS4로 CSS select 하기

예제에 있듯이 select, select_one 두개의 메소드를 이용가능 합니다. select 메소드를 사용하게 되면 선택한 tag가 여러개라는 가정하에 list 형태로 묶이게 됩니다.

select_one 메소드는 첫번째 결과만 tag에 저장하게 됩니다.

결과로 돌아오는 tag는 기본적으로 text, href 를 이용하여 직접 읽을 수도 있지만 get 을 이용하여 읽을 수도 있습니다. 

아래 부분 참고

print(f'type:{type(one)},result:{one},href:{one.href},text:{one.text},get:{one.get("data-tiara-layer")}')

결론적으로 우리가 원하는 텍스트는 one.text에 저장되게 됩니다.


2021년 10월 8일 금요일

python BeautifulSoup 기초 사용법과 실전 예제 (BeautifulSoup basic usage and practical examples)

 BeautifulSoup 를 사용할때마다 느끼는 거지만 뭔가 꽉막힌듯한 뭐부터 해야하나 고민을 할때가 많습니다. 어떤 한값을 읽어내는건 단순합니다. 그러나 웹크롤링이나 뭔가 복잡하게 표를 읽어내는건 고민을 좀 해야하는 부분도 많고 해서, 그래서 실전 예제를 통해서 제가 사용하는 절차를 정리해 보았습니다.

여기에서 사용할 예제는 https://stackoverflow.com/tags 여기입니다.

그사이 tags 정보가 변경되어 예제가 동작이 되지 않을 수도 있는점 참고 부탁드립니다.


목표

목표는 tags의 값을 모으는 작업입니다.


위와 같은 html 있다면 아래와 같은 dict 형태의 결과를 얻도록 하는것이 이번 목표입니다.
{'javascript':[2280764,808,4505], 'python':[1809544,1089,6298], 'java' ....


1. 정보를 가져오고자 하는 웹 페이지의 HTML 저장

간단하게 코드를 만들었습니다. 원하는 page는 test.html에 저장됩니다.

import requests

def write_file(filename,str_data):
	with open(filename, 'w', encoding='utf-8') as fp:
		fp.write(str_data)
		fp.close()
		
resp = requests.get('https://stackoverflow.com/tags')
write_file("test.html",resp.text)


2. 반복된는 내용을 구하자고 할때 한개의 set 분의 데이터를 얻을수 있는 최상위 html tag를 찾음

1개의 세트분이라고 한다면 여기에서는 'javascript':[2280764,808,4505] 이 데이터를 얻을 수 있는 내용이 되겠습니다. 이 부분을 찾는 방법은 친절하게 html파일을 텍스트로 열어서 해당되는 글자를 읽어서 찾으면 편합니다. 여기에서는 "For questions"가 고유한 text이니 해당 글씨 앞쪽으로 보면 될듯합니다.

찾았습니다. 몇줄위를 보면 원하는 "javascript" 부분도 있습니다. 그러면 이중에 어디가 한개의 set 분의 데이터인지 알 수 있을까요? 솔직하게 얘기하자면 사실 알기는 힘듭니다.


크롬의 F12를 눌러서 찾는 방법도 있습니다. 위 화면을 보자면 <div class="s-card js-tag-cell d-flex fd-column"> 여기가 원하는 결과의 한개분의 SET 위치가 되겠습니다.

3. 한개분의 셋트 위치 TAG로 find_all 모두 찾기

2번에서 정확하게 찾지 못하더라도 3번과 2번을 반복함으로서 정확한 위치를 찾을 수 있습니다.
여기에서 답을 알고 있을지라도, 모르는척 해보겠습니다.
즉 <div class="d-flex jc-space-between ai-center mb12"> 이런값이 정답인지 아닌지 확인해보도록 하겠습니다.

저장된 test.html 을 읽어와 <div class="d-flex jc-space-between ai-center mb12"> tag를 find_all로 찾아서 해당 부분이 제대로 나오는지 확인하는 코드입니다.
import requests
from bs4 import BeautifulSoup

def bs_example(data):
	soup = BeautifulSoup(data,"html.parser")
	#<div class="d-flex jc-space-between ai-center mb12">
	sresult = soup.find_all("div",attrs={"class":"d-flex jc-space-between ai-center mb12"})
	for rone in sresult:
		print(rone)
		print("##########################")
	

def write_file(filename,str_data):
	with open(filename, 'w', encoding='utf-8') as fp:
		fp.write(str_data)
		fp.close()
		

if __name__ == "__main__":
	#resp = requests.get('https://stackoverflow.com/tags')
	#bs_example(resp.text)
	#write_file("test.html",resp.text)
	bs_example(open("test.html",encoding='utf-8'))

결과

##### 이건 구분선입니다. 내용이 많아서 앞쪽에 3개만 출력해봤습니다. 

보시면 javascript , python , java 이런것은 포함되어있음을 알 수 있는데 나머지 수치적인 정보는 빠져있음을 알 수 있습니다. 그렇다면 좀 더 앞쪽 tag를 가져오도록 하겠습니다.

<div class="d-flex jc-space-between ai-center mb12">
<div class="flex--item">
<a class="post-tag" href="/questions/tagged/javascript" rel="tag" title="show questions tagged 'javascript'">javascript</a>
</div>
</div>
##########################
<div class="d-flex jc-space-between ai-center mb12">
<div class="flex--item">
<a class="post-tag" href="/questions/tagged/python" rel="tag" title="show questions tagged 'python'">python</a>
</div>
</div>
##########################
<div class="d-flex jc-space-between ai-center mb12">
<div class="flex--item">
<a class="post-tag" href="/questions/tagged/java" rel="tag" title="show questions tagged 'java'">java</a>
</div>
</div>


이번에 앞쪽에 있는 <div class="s-card js-tag-cell d-flex fd-column"> 이것입니다.

변경된 코드입니다.

import requests
from bs4 import BeautifulSoup

def bs_example(data):
	soup = BeautifulSoup(data,"html.parser")
	#<div class="s-card js-tag-cell d-flex fd-column">
	sresult = soup.find_all("div",attrs={"class":"s-card js-tag-cell d-flex fd-column"})
	for rone in sresult:
		print(rone)
		print("##########################")
	

def write_file(filename,str_data):
	with open(filename, 'w', encoding='utf-8') as fp:
		fp.write(str_data)
		fp.close()
		

if __name__ == "__main__":
	#resp = requests.get('https://stackoverflow.com/tags')
	#bs_example(resp.text)
	#write_file("test.html",resp.text)
	bs_example(open("test.html",encoding='utf-8'))


내용이 많아서 첫번째 내용 결과만 확인 해보겠습니다. 아래 내용 보면 <div class="flex--item">2280774 questions</div> title="810 questions tagged javascript in the last 24 hours" title="4505 questions tagged javascript in the last 7 days" 이런 부분들 모두 포함 되었음을 알 수 있습니다.

<div class="s-card js-tag-cell d-flex fd-column">
<div class="d-flex jc-space-between ai-center mb12">
<div class="flex--item">
<a class="post-tag" href="/questions/tagged/javascript" rel="tag" title="show questions tagged 'javascript'">javascript</a>
</div>
</div>
<div class="flex--item fc-medium mb12 v-truncate4">

                        For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Please include all relevant tags on your question; e.g., [node.js],…

                    </div>
<div class="mt-auto d-flex jc-space-between fs-caption fc-black-400">
<div class="flex--item">2280774 questions</div>
<div class="flex--item s-anchors s-anchors__inherit"> <a href="/questions/tagged/javascript?sort=newest&amp;days=1" title="810 questions tagged javascript in the last 24 hours">810 asked today</a>, <a href="/questions/tagged/javascript?sort=newest&amp;days=7" title="4505 questions tagged javascript in the last 7 days">4505 this week</a> </div>
</div>
</div>
##########################

결국 정답은 <div class="s-card js-tag-cell d-flex fd-column"> 이것 이었습니다.


4. 세부 항목들 찾기

이제 내부적으로는 find를 사용합니다. find_all 한 결과 변수를 for loop를 이용해서 사용하면 됩니다.

 위 결과에서 찾아야 하는 부분은 javascript 라는 text인데 <a class="post-tag" href="/questions/tagged/javascript" rel="tag" title="show questions tagged 'javascript'">javascript</a> 여기에 있습니다.

정리해보겠습니다.

4.1 javascript : 결과에서 <a class="post-tag" href="/questions/tagged/javascript" rel="tag" title="show questions tagged 'javascript'">javascript</a> => a class="post-tag"

4.2 전체 questions : 결과에서 <div class="flex--item">2280774 questions</div> => div class="flex--item"

4.3 asked today : 결과에서 <a href="/questions/tagged/javascript?sort=newest&amp;days=1" title="810 questions tagged javascript in the last 24 hours">810 asked today</a> => a href="/questions/tagged/javascript?sort=newest&amp;days=1"

4.4 this week : 결과에서 <a href="/questions/tagged/javascript?sort=newest&amp;days=7" title="4505 questions tagged javascript in the last 7 days">4505 this week</a> => a href="/questions/tagged/javascript?sort=newest&amp;days=7"

그럼 4.2를 제외한 코드를 만들어 보겠습니다.

막상 해보면 href 쪽에 각각의 내용이 변경됩니다. 따라서 검색하려면 조금 변경이 필요한데 re 패키지를 이용하여 정규식 매칭도 지원하고 있어서 이 부분은 간단하게 해결하였습니다.

이 부분입니다. herf 에서 days=1,days=7 이라는 문자열이 있는곳을 찾게 됩니다.

print(rone.find("a",href=re.compile(r"days=1")))

print(rone.find("a",href=re.compile(r"days=7")))

import requests
from bs4 import BeautifulSoup
import re

def bs_example(data):
	soup = BeautifulSoup(data,"html.parser")
	#<div class="s-card js-tag-cell d-flex fd-column">
	sresult = soup.find_all("div",attrs={"class":"s-card js-tag-cell d-flex fd-column"})
	for rone in sresult:
		#print(rone)
		print(rone.find("a",attrs={"class":"post-tag"}))
		print(rone.find("a",href=re.compile(r"days=1")))
		print(rone.find("a",href=re.compile(r"days=7")))
		print("##########################")

def write_file(filename,str_data):
	with open(filename, 'w', encoding='utf-8') as fp:
		fp.write(str_data)
		fp.close()
		

if __name__ == "__main__":
	#resp = requests.get('https://stackoverflow.com/tags')
	#bs_example(resp.text)
	#write_file("test.html",resp.text)
	bs_example(open("test.html",encoding='utf-8'))

실행 결과

<a class="post-tag" href="/questions/tagged/javascript" rel="tag" title="show questions tagged 'javascript'">javascript</a>
<a href="/questions/tagged/javascript?sort=newest&amp;days=1" title="810 questions tagged javascript in the last 24 hours">810 asked today</a>
<a href="/questions/tagged/javascript?sort=newest&amp;days=7" title="4505 questions tagged javascript in the last 7 days">4505 this week</a>
##########################
<a class="post-tag" href="/questions/tagged/python" rel="tag" title="show questions tagged 'python'">python</a>
<a href="/questions/tagged/python?sort=newest&amp;days=1" title="1093 questions tagged python in the last 24 hours">1093 asked today</a>
<a href="/questions/tagged/python?sort=newest&amp;days=7" title="6304 questions tagged python in the last 7 days">6304 this week</a>

다음으로 미루어 두었던 4.2입니다.

이건 div class="flex--item" tag가 많아서 미루었습니다. 찾는 방법이 어려가지가 존재할 수는 있는데 find_all 로 찾아서 text가 questions가 없으면 버리는 형태로 구현해 보겠습니다.

<div class="flex--item fc-medium mb12 v-truncate4"> 문제는 이러한 경우도 match가 되게 됩니다. 

이걸 적당히 만들어보면 이렇습니다. text에는 questions 문자열이 있고 class에는 fc-medium 없는 조건입니다.

이렇게 되면 text가 "숫자 questions" 로 올라오게 되는데 split 로 나누고 첫번째 항목만 가져와서 int 형태로 변환하면 됩니다.

def find_questions(bs_data):
	bsret = bs_data.find_all("div",attrs={"class":"flex--item"})
	for bsone in bsret:
		if ("questions" in bsone.text) and not ("fc-medium" in bsone.get("class")):
			#print(bsone.text)
			return int(bsone.text.strip().split(" ")[0].strip())

이제 구현은 어느정도 마무리 되었습니다.

def bs_example(data):
	soup = BeautifulSoup(data,"html.parser")
	#<div class="s-card js-tag-cell d-flex fd-column">
	sresult = soup.find_all("div",attrs={"class":"s-card js-tag-cell d-flex fd-column"})
	for rone in sresult:
		#print(rone)
		print(rone.find("a",attrs={"class":"post-tag"}).text)
		print(find_questions(rone))
		print(int(rone.find("a",href=re.compile(r"days=1")).text.strip().split(" ")[0].strip()))
		print(int(rone.find("a",href=re.compile(r"days=7")).text.strip().split(" ")[0].strip()))
		print("##########################")

dict 로 결과를 모으고 출력해보도록 하겠습니다.

전체 소스입니다.

import requests
from bs4 import BeautifulSoup
import re

def find_questions(bs_data):
	bsret = bs_data.find_all("div",attrs={"class":"flex--item"})
	for bsone in bsret:
		if ("questions" in bsone.text) and not ("fc-medium" in bsone.get("class")):
			return int(bsone.text.strip().split(" ")[0].strip())

def bs_example(data):
	ret = {}
	soup = BeautifulSoup(data,"html.parser")
	sresult = soup.find_all("div",attrs={"class":"s-card js-tag-cell d-flex fd-column"})
	for rone in sresult:
		#print(rone)
		#print(rone.find("a",attrs={"class":"post-tag"}).text)
		#print(find_questions(rone))
		#print(int(rone.find("a",href=re.compile(r"days=1")).text.strip().split(" ")[0].strip()))
		#print(int(rone.find("a",href=re.compile(r"days=7")).text.strip().split(" ")[0].strip()))
		#print("##########################")
		tag = rone.find("a",attrs={"class":"post-tag"}).text
		ret[tag] = [find_questions(rone),int(rone.find("a",href=re.compile(r"days=1")).text.strip().split(" ")[0].strip()),int(rone.find("a",href=re.compile(r"days=7")).text.strip().split(" ")[0].strip())]
	return ret

def write_file(filename,str_data):
	with open(filename, 'w', encoding='utf-8') as fp:
		fp.write(str_data)
		fp.close()
		
def f1(x):
	return x[1][1]
def f2(x):
	return x[1][2]

def print_dict(dict_data):
	for key,data in dict_data:
		print(key,data)

if __name__ == "__main__":
	USE_FILE = False
	if USE_FILE:
		ret = bs_example(open("test.html",encoding='utf-8'))
	else:
		resp = requests.get('https://stackoverflow.com/tags')
		write_file("test.html",resp.text)
		ret = bs_example(resp.text)
		
	print(ret)
	print("------------------ sorted days=1")
	data_ = sorted(ret.items(),key=f1,reverse=True)
	print_dict(data_)
	print("------------------ sorted days=7")
	data_ = sorted(ret.items(),key=f2,reverse=True)
	print_dict(data_)

결과입니다.

누적은 javascript이지만 1일 최고 순위는 python 입니다. 7일을 봐도 python입니다.

{'javascript': [2280813, 801, 4554], 'python': [1809580, 1096, 6387], 'java': [1803186, 417, 2377], 'c#': [1501073, 319, 1687], 'php': [1416808, 245, 1430], 'android': [1350753, 264, 1526], 'html': [1094188, 267, 1716], 'jquery': [1018332, 71, 467], 'c++': [739969, 238, 1229], 'css': [734575, 169, 1168], 'ios': [661851, 109, 575], 'mysql': [633104, 114, 678], 'sql': [606868, 194, 991], 'r': [421633, 217, 1244], 'node.js': [404352, 223, 1266], 'arrays': [374060, 109, 686], 'c': [365866, 102, 588], 'asp.net': [364865, 33, 196], 'reactjs': [336134, 332, 2032], 'ruby-on-rails': [328553, 24, 156], 'json': [326916, 100, 571], '.net': [311611, 76, 316], 'sql-server': [309938, 74, 416], 'swift': [303335, 103, 548], 'python-3.x': [296404, 165, 929], 'objective-c': [291942, 13, 50], 'django': [276511, 110, 727], 'angular': [263495, 168, 827], 'angularjs': [262258, 12, 71], 'excel': [252456, 86, 454], 'regex': [244622, 67, 363], 'ruby': [221270, 21, 122], 'iphone': [221208, 9, 31], 'pandas': [218023, 168, 1020], 'ajax': [217443, 23, 154], 'linux': [208966, 79, 398]}
------------------ sorted days=1
python [1809580, 1096, 6387]
javascript [2280813, 801, 4554]
java [1803186, 417, 2377]
reactjs [336134, 332, 2032]
c# [1501073, 319, 1687]
html [1094188, 267, 1716]
android [1350753, 264, 1526]
php [1416808, 245, 1430]
c++ [739969, 238, 1229]
node.js [404352, 223, 1266]
r [421633, 217, 1244]
sql [606868, 194, 991]
css [734575, 169, 1168]
angular [263495, 168, 827]
pandas [218023, 168, 1020]
python-3.x [296404, 165, 929]
mysql [633104, 114, 678]
django [276511, 110, 727]
ios [661851, 109, 575]
arrays [374060, 109, 686]
swift [303335, 103, 548]
c [365866, 102, 588]
json [326916, 100, 571]
excel [252456, 86, 454]
linux [208966, 79, 398]
.net [311611, 76, 316]
sql-server [309938, 74, 416]
jquery [1018332, 71, 467]
regex [244622, 67, 363]
asp.net [364865, 33, 196]
ruby-on-rails [328553, 24, 156]
ajax [217443, 23, 154]
ruby [221270, 21, 122]
objective-c [291942, 13, 50]
angularjs [262258, 12, 71]
iphone [221208, 9, 31]
------------------ sorted days=7
python [1809580, 1096, 6387]
javascript [2280813, 801, 4554]
java [1803186, 417, 2377]
reactjs [336134, 332, 2032]
html [1094188, 267, 1716]
c# [1501073, 319, 1687]
android [1350753, 264, 1526]
php [1416808, 245, 1430]
node.js [404352, 223, 1266]
r [421633, 217, 1244]
c++ [739969, 238, 1229]
css [734575, 169, 1168]
pandas [218023, 168, 1020]
sql [606868, 194, 991]
python-3.x [296404, 165, 929]
angular [263495, 168, 827]
django [276511, 110, 727]
arrays [374060, 109, 686]
mysql [633104, 114, 678]
c [365866, 102, 588]
ios [661851, 109, 575]
json [326916, 100, 571]
swift [303335, 103, 548]
jquery [1018332, 71, 467]
excel [252456, 86, 454]
sql-server [309938, 74, 416]
linux [208966, 79, 398]
regex [244622, 67, 363]
.net [311611, 76, 316]
asp.net [364865, 33, 196]
ruby-on-rails [328553, 24, 156]
ajax [217443, 23, 154]
ruby [221270, 21, 122]
angularjs [262258, 12, 71]
objective-c [291942, 13, 50]
iphone [221208, 9, 31]