http://habrahabr.ru/post/271231/
Как и многие любители путешествовать, я нахожу координаты достопримечательностей городов на сайте
autotravel.ru (далее — сайт). Под свои нужды написал небольшую утилиту для скачивания файлов с достопримечательностями для последующей заливки в навигатор. Программа предельно простая, но работает именно так, как мне было нужно. Кроме того реализовано простейшее средство экономии времени загрузки и траффика — кэширование.
Программа, которую я назвал AtTrackDownloader, написана на Python 3 с использованием Beautiful Soup — библиотеки для синтаксического разбора файлов HTML. Для графического интерфейса используется PyQt — просто потому, что я знаком с Qt.
Ядром программы является класс autotravel. Полностью расписывать его методы смысла не имеет (тем более в конце дам ссылку на git репозитарий). Распишу основную логику.
def __load_towns_page(self, letter):
url = AutoTravelHttp + '/towns.php'
params = {'l' : letter.encode('cp1251')}
req = urllib2.Request(url, urllib.parse.urlencode(params).encode('cp1251'))
response = urllib2.urlopen(req)
return response.read().decode('utf-8')
Метод __load_towns_page загружает содержимое страницы сайта для городов, начинающихся на букву letter. Точнее так было несколько месяцев назад, а какое-то время назад они поменяли адреса страниц и вместо буквы там указывается 'a' + номер_буквы. Например: А — 'a01', Б — 'a02', ..., Я — 'a30'.
Соответственно, для загрузки городов на все буквы это метод вызывается в цикле из метода __load_all_towns. Полученный текст веб-страницы передается в метод __load_towns_from_url:
def __load_towns_from_url(self, html_src):
for line in html_src.splitlines():
soup = BeautifulSoup(line, 'html.parser')
area = soup.find('font', {'class' : 'travell0'})
town= soup.find('a', {'class', 'travell5'})
if town == None:
town = soup.find('a', {'class', 'travell5c'})
if town == None:
continue
town_name = town.get_text().strip()
area_name = area.get_text().strip()[1:-1]
town_href = town.get('href')
yield {'area' : area_name, 'town' : town_name, 'href' : town_href}
В этом методе идет вся основная работа по парсингу городов. Разобранные города заносятся в список словарей с ключами 'area' — область или страна, 'town' — название города, 'href' — коротка ссылка на город (относительно сайта).
Таким образом происходит загрузка всех городов с сайта в память. Это занимает секунд 10 на моем компьютере. Естественно, это — плохо, поэтому возникла идея кэша загруженных городов. Python позволяет сериализовать список одной строкой, в отличие от других известных мне языков, что здорово. Метод сохранения городов в файл кэша выглядит следующим образом:
def __save_to_cache(self, data):
with open('attd.cache', 'wb') as f:
pickle.dump(data, f)
Загрузка из кэша выполняется в конструкторе класса:
def __init__(self):
try:
with open('attd.cache', 'rb') as f:
self.__all_towns = pickle.load(f)
except FileNotFoundError:
self.__all_towns = list(self.__load_all_towns())
self.__save_to_cache(self.__all_towns)
В итоге получаем десятисекундную задержку только при первом запуске.
Получение ссылок на файлы треков реализовано в методе get_towns_track_links, который принимает адрес страницы с городом:
def get_towns_track_links(self, href):
req = urllib2.Request(href)
response = urllib2.urlopen(req)
soup = BeautifulSoup(response.read().decode('utf-8'), 'html.parser')
r = {}
for link in soup.findAll('a', {'class' : 'travell5m'}):
if link.get_text() == 'GPX':
r['gpx'] = AutoTravelHttp + link.get('href')
elif link.get_text() == 'KML':
r['kml'] = AutoTravelHttp + link.get('href')
elif link.get_text() == 'WPT':
r['wpt'] = AutoTravelHttp + link.get('href')
return r
Поскольку, теоретически (и раньше такие случаи были) на страницы города может не быть какого-то формата трека, то доступные типы треков и ссылки на них заносятся в словарь. Потом это используется для фильтра файлов в диалоге сохранения файлов.
Описывать создание интерфейса не вижу смысла — там все еще проще, кому интересно могут пройти в репозитарий. А для тех, кому не интересно, привожу скриншот:
Полные исходные коды находятся на
Github.