четверг, 29 марта 2012 г.

О том как написать программу на lua для приема сводок погоды с любого сайта. Часть 2


Вытаскиваем прогноз погоды на несколько суток

Продолжим писать скрипт. Мы имеем скрипт, забирающий прогноз с сайта погоды, и файл pogoda с данными.

Но сначала о некоторых специальных символах, которые мы будем использовать.

.*        -- означает любой знак включая переносы строк
%а      -- одна буква
%а*     -- одно слово
%d       -- одно число
%d*     -- несколько чисел
%p       -- знак препинания
%s       --  пробел
( )         -- всё, что находится между скобками будет забрано

Эти символы мы будем использовать для обозначения данных которые необходимо выбрать из текста прогноза.

Сначала отредактируем скрипт forecast.lua. Теперь нам нет необходимости каждый раз при обновлении прогноза записывать его в файл pogoda, для дальнейшей работы хватит уже существующего. Нам необходимо полученные данные сохранить в памяти для дальнейшего использования.

В скрипте коментируем строку, направляющую полученные данные в файл pogoda и вписываем строки

-- для проверки загоняем в /tmp
-- os.execute("curl --max-time 60 '".. web .."'| sed 's/%//g' >> ~/proba/pogoda1")

-- считываем данные с сайта
local f = io.popen("curl --max-time 60 '"..web.."' | sed 's/%//g'")
-- помещаем в переменную allweatherdata
allweatherdata = f:read("*a")
-- прекращаем считывание
f:close()
-- фильтруем данные в переменной allweatherdata
allweatherdata = string.gsub(allweatherdata, "[\n\r]", "")

Теперь все данные со страницы сайта находятся в переменной allweatherdata.

Далее будем знакомиться с тем, что получили с сайта погоды. Если сайт даёт данные на русском языке, то считайте, что вам крупно повезло, просто найдите строки с нужными данными. Но обычно сайты набиты информацией на английском языке. Все прогнозы в чем то сходятся. Построены примерно по одному принципу и кроме самого прогноза содержат много не нужной для нас информации. Для начала будем искать прогноз погоды на несколько дней. Открываем полученный файл pogoda и ищем строку в которой написано что то вроде этого

        <div class="forecastTitle">7-day Forecast / 10-day Forecast</div>

Ниже этой строки идут данные прогноза на несколько суток и их необходимо забрать, если конечно они нам нужны, и эти данные надо разобрать и разложить "по полочкам". Для хранения данных между обновлениями прогноза в дальнейшем создадим необходимые массивы данных.

Просматривая далее текст ищем строку, с которой начинается прогноз на день, и начинается он с названия дня недели. В моём случае она выглядит так

<td colspan="2" class="forecastDayOfWeekTitle">Monday</td>

Далее расположены строки с данными прогноза на этот день, выравнено мной для лучшего просмотра

</tr>
<tr>
<td class="forecastWeatherIcon" width="30" align="center">
<img src="/weather_icons/snow_ds.gif" width="39" height="39">
</td>
<td class="forecastTemperature">High: 37&deg;C<br/>Low: 24&deg;C</td> 
</tr>
<tr>
<td colspan="2" class="forecastWeather" style="border-bottom: 1px dotted #3bb878">Light snow<br>&nbsp;</td>
</tr>

</table>
</td>
<td align="center">
<table width="135" height="100" border="0" cellpadding="0" cellspacing="0">
<tr>

и начинается прогноз на следующий день

<td colspan="2" class="forecastDayOfWeekTitle">Tuesday</td>

а так как прогноз выдает одни и теже данные для всех дней, с разными параметрами, то просматривать следующие дни не имеет смысла.

Итак у нас имеется:

День недели

class="forecastDayOfWeekTitle">Monday</td>

Иконка погоды

<img src="/weather_icons/snow_ds.gif" width="39" height="39">

Температура самая высокая и самая низская

class="forecastTemperature">High: 37&deg;C<br/>Low: 24&deg;C</td>

Состояние погоды

class="forecastWeather" style="border-bottom: 1px dotted #3bb878">Light snow<br>&nbsp;</td>


Значит всего пять данных, пять потому что данные двух температур.

Создаем базы данных где будут храниться данные прогноза между обновлениями

forecast_day = {}
weather_icon = {}
high_temp = {}
low_temp = {}
forecastWeather = {}

Если у вас возможно получение большего количества данных, то добавляем строки в базы данных. 

Теперь необходимо найти место, где заканчивается прогноз. Нам не нужны лишние данные. Просматривая далее текст прогноза находим последнее упоминание состояния погоды, у меня это

                           <td colspan="2" class="forecastWeather" style="border-bottom: 1px dotted #3bb878">Light drizzle<br>&nbsp;</td>
                        </tr>

                    </table>
                </td>
            </tr>

        </table>



<div style="border:1px solid gray;">
<script type="text/javascript"><!--

google_ad_client = "pub-2406273870314670";

Для обозначения окончания места прогноза возьмем строку

<div style="border:1px solid gray;">

просто по тому, что такой текст не встречается в тексте прогноза, а текст нам необходимо забрать.

Нам нужны все данные между

<div class="forecastTitle">

и конечной метки

<div style="border:1px solid gray;">

С начальной и конечной метками разобрались, а для обозначения содержащихся между метками символами используем специальный символ (.*)

В скрипт пишем строку, которая заберет все данные прогноза на несколько дней из переменной allweatherdata в переменную allweather. А так как в выбранных нами метках присутствуют кавычки, то перед кавычками в метках необходимо поставить обратный слеш

local a,b,allweather = string.find(allweatherdata,"<div class=\"forecastTitle\">(.*)<div style=\"border:1px solid gray;\">")

Теперь все данные прогноза будут находиться в переменной allweather. Можно, конечно, использовать и весь текст, забранный с сайта, но короткий текст быстрее обрабатывается. Нет необходимости читать то, что мы использовать не будем.

Итак текст прогноза в переменной allweather

Начнем выбирать необходимые нам данные и начнем с дня недели. Как мы уже видели, в тексте прогноза, строка дня недели выглядит так

class="forecastDayOfWeekTitle">Monday</td>

Для выдергивания названия дня используем (%a*) и наша строка будет иметь такой вид

local s,f,t = string.find(allweather, "forecastDayOfWeekTitle\">(%a*)<")

Следующая строка помещает данные в таблицу, в ячейку forecast_day

table.insert(forecast_day, t)

но в этом случае будет взято название только первого встретившегося дня. Для забора всех встретившихся названий дней нам необходимо создать цикл, который просмотрит всю переменную и выберет все названия дней. Это будет выглядеть так

-- очищаем счетчик
local start = 0
local f = 1
-- сам цикл
while f ~= nil do
-- данные о названии дней
local s,f,t = string.find(allweather, "forecastDayOfWeekTitle\">(%a*)<", start)
-- помещаем данные в таблицу в forecast_day
table.insert(forecast_day, t)
-- если данных больше нет заканчиваем цикл
if f == nil then break end
-- если данные имеются, начинаем сначала
start = f
-- закрываем цикл
end

А как проверить, те ли данные взяты, да и взяты ли они? Для этого ниже цикла, перед закрывающим цикл end, помещаем команду

print(t)

которая выведет в терминал выбранное значение

Скрипт на данный момент выглядит так

--[[ forecast.lua
скрипт забирает прогноз погоды и обрабатывает его для вывода на экран
]]
require "cairo"
-- адрес сайта
web = "http://www.weatherreports.com/Pavlograd,_Ukraine?units=c"
-- web = "http://www.pogodaspb.info"
-- время обновления прогноза погоды
secs = 1800 -- обновление каждые 30 минут ( в секундах )
function conky_forecast ()
-- устанавливаем таймер обновления данных с сайта
local updates = tonumber(conky_parse('${updates}'))
local timer = (updates %secs) + 1
if timer == 1 or updates == 0 then
-- для проверки загоняем в /tmp
-- os.execute("curl --max-time 60 '".. web .."'| sed 's/%//g' >> ~/proba/pogoda1")
-- считываем данные с сайта
local f = io.popen("curl --max-time 60 '"..web.."' | sed 's/%//g'")
-- помещаем в переменную allweatherdata
allweatherdata = f:read("*a")
-- прекращаем считывание
f:close()
-- фильтруем данные в переменной allweatherdata
allweatherdata = string.gsub(allweatherdata, "[\n\r]", "")
-- создаем таблицу для прогноза
forecast_day = {}
weather_icon = {}
high_temp = {}
low_temp = {}
 forecastWeather = {}
-- выбираем данные для прогноза
local a,b,allweather = string.find(allweatherdata,"<div class=\"forecastTitle\">(.*)<div style=\"border:1px solid gray;\">")
--- очищаем счетчик
local start = 0
local f = 1
-- сам цикл
while f ~= nil do
-- данные о названии дней
local s,f,t = string.find(allweather, "forecastDayOfWeekTitle\">(%a*)<", start)
-- помещаем данные в таблицу в forecast_day
table.insert(forecast_day, t)
-- если данных больше нет заканчиваем цикл
if f == nil then break end
-- если данные имеются, начинаем сначала
start = f
print(t)
end
end
end

Запускаем в терминале и видим результат

olgmen@olgmen:~$ conky -c ~/proba/proba_conkyrc
Conky: desktop window (15a) is root window
Conky: window type - normal
Conky: drawing to created window (0x2e00001)
Conky: drawing to double buffer
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 49906  100 49906    0     0   8040      0  0:00:06  0:00:06 --:--:-- 61612
Wednesday
Thursday
Friday
Saturday
Sunday

Как сказал бы Кот Матроскин -- СРАБОТАЛО

Правда названия дней на английском, но далее я покажу как сделать вывод на любом языке.

По такому же принципу вытаскиваем данные о состоянии погоды, правда с небольшими изменениями. Эти изменения связаны с тем, что состояние погоды может передаваться несколькими словами, например, слабый снег. Поэтому добавляем знак заменяющий пробел и помещаем необходимые данные в квадратные скобки

Строка состояния погоды

class="forecastWeather" style="border-bottom: 1px dotted #3bb878">Light snow<br>&nbsp;</td>

Всю строку брать не будем, возьмем ближайшую часть текста

local s,f,t = string.find(allweather, "dotted #3bb878\">([%a%s]*)<")
table.insert(forecastWeather, t)

Добавляем в цикл строки и проверяем

-- очищаем счетчик
local start = 0
local f = 1
-- сам цикл
while f ~= nil do
-- данные о названии дней
local s,f,t = string.find(allweather, "forecastDayOfWeekTitle\">(%a*)<", start)
-- помещаем данные в таблицу в forecast_day
table.insert(forecast_day, t)
-- состояние погоды
local s,f,t = string.find(allweather, "dotted #3bb878\">([%a%s]*)<", start)
table.insert(forecastWeather, t)
-- если данных больше нет заканчиваем цикл
if f == nil then break end
-- если данные имеются, начинаем сначала
start = f
print(t)
end

результат проверки

olgmen@olgmen:~$ conky -c ~/proba/proba_conkyrc
Conky: desktop window (15a) is root window
Conky: window type - normal
Conky: drawing to created window (0x2e00001)
Conky: drawing to double buffer
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 49922  100 49922    0     0   7728      0  0:00:06  0:00:06 --:--:-- 55101
Partly Cloudy 
Light drizzle
Cloudy 
Patchy rain nearby
Light rain shower

Аналогично забираем название иконки погоды без расширения .gif, так как иконки с сайта забрать проблематично, при выводе подсунем свои, но нам надо знать какие именно

local s,f,t = string.find(allweather, "weather_icons/([%a%p]*).gif", start)
table.insert(weather_icon, t)

Теперь забираем температуру

-- наибольшая температура
local s,f,t = string.find(allweather, "High:%s*([%p%d]*)&deg", start)
table.insert(high_temp, t)

Проверяем

olgmen@olgmen:~$ conky -c ~/proba/proba_conkyrc
Conky: desktop window (15a) is root window
Conky: window type - normal
Conky: drawing to created window (0x3000001)
Conky: drawing to double buffer
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 49926  100 49926    0     0  34051      0  0:00:01  0:00:01 --:--:-- 47055
47
50
46
47
41

Ого, этого не может быть !!! Хоть Павлоград и южнее Питера, но не в Африке. Такие значения больше подходят к Фаренгейту. Это мы исправим при выводе значения на экран.

Аналогично выводим значения наинизшей температуры

local s,f,t = string.find(allweather, "Low:%s*([%p%d]*)&deg", start)
table.insert(low_temp, t)

После всех редактирований скрипт стал иметь такой вид

--[[ forecast.lua
скрипт забирает прогноз погоды и обрабатывает его для вывода на экран
]]
require "cairo"
-- адрес сайта
web = "http://www.weatherreports.com/Pavlograd,_Ukraine?units=c"
-- web = "http://www.pogodaspb.info"
-- время обновления прогноза погоды
secs = 1800 -- обновление каждые 30 минут ( в секундах )

function conky_forecast ()

-- устанавливаем таймер обновления данных с сайта
local updates = tonumber(conky_parse('${updates}'))
local timer = (updates %secs) + 1
if timer == 1 or updates == 0 then
-- для проверки загоняем в /tmp
-- os.execute("curl --max-time 60 '".. web .."'| sed 's/%//g' >> ~/proba/pogoda1")

-- считываем данные с сайта
local f = io.popen("curl --max-time 60 '"..web.."' | sed 's/%//g'")
-- помещаем в переменную allweatherdata
allweatherdata = f:read("*a")
-- прекращаем считывание
f:close()
-- фильтруем данные в переменной allweatherdata
allweatherdata = string.gsub(allweatherdata, "[\n\r]", "")

-- создаем таблицу для прогноза
forecast_day = {}
weather_icon = {}
high_temp = {}
low_temp = {}
forecastWeather = {}
-- выбираем данные для прогноза
local a,b,allweather = string.find(allweatherdata,"<div class=\"forecastTitle\">(.*)<div style=\"border:1px solid gray;\">")
--- очищаем счетчик
local start = 0
local f = 1
-- сам цикл
while f ~= nil do
-- данные о названии дней
local s,f,t = string.find(allweather, "forecastDayOfWeekTitle\">(%a*)<", start)
-- помещаем данные в таблицу в forecast_day
table.insert(forecast_day, t)
local s,f,t = string.find(allweather, "dotted #3bb878\">([%a%s]*)<", start)
table.insert(forecastWeather, t)
-- иконка погоды
local s,f,t = string.find(allweather, "weather_icons/([%a%p]*).gif", start)
table.insert(weather_icon, t)
-- наибольшая температура
local s,f,t = string.find(allweather, "High:%s*([%p%d]*)&deg", start)
table.insert(high_temp, t)
-- наинизшая температура
local s,f,t = string.find(allweather, "Low: ([%p%d]*)&deg", start)
table.insert(low_temp, t)
-- если данных больше нет заканчиваем цикл
if f == nil then break end
-- если данные имеются, начинаем сначала
start = f
end
end
end

Далее будем вытаскивать данные текущей погоды

Конец второй части

Комментариев нет:

Отправить комментарий