Created
January 18, 2017 12:56
-
-
Save gigovich/915f36efb10038ba9bb66f0a203cdfd5 to your computer and use it in GitHub Desktop.
Golang - представьте это на Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"flag" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"net/http" | |
"os" | |
"os/signal" | |
"regexp" | |
"strings" | |
"time" | |
) | |
var ( | |
flagParallel = flag.Int("p", 5, "количество одновременно работающих запросов") | |
) | |
func main() { | |
// спарсим аргси из команды запуска | |
flag.Parse() | |
// если ты не дебил, и указал хотя-бы один параметр, будем считать его за стартовый урл | |
if flag.NArg() == 0 { | |
fmt.Println("usage: crawl [OPTIONS] <START_URL>") | |
fmt.Println() | |
flag.PrintDefaults() | |
os.Exit(1) | |
} | |
// это пустой канал с пустой структурой, мы в него ничего писать не будем, заставим все горутины, | |
// читать из него, а в конце программы закроем, как только горутины попытаются прочитать из закрытого канала, | |
// заставим их завершиться. | |
live := make(chan struct{}) | |
// запускаем карулер функцию, которая возвращает канал с будущими статусами, а внутри себя запускает | |
// асинхронную горутину которая и будет делать все дела, и слать данные в этот канал. | |
stat := start(live, flag.Arg(0), *flagParallel) | |
// сами запускаем асинхронно горутину котора печатает статистику каждую минуту | |
go printStat(live, stat) | |
// создаём пустой канал для того, что-бы ловить сигнал ОС об завершении | |
notifCh := make(chan os.Signal) | |
// просим чувака половить сигналы о завершении и отослать их по нашему каналу | |
signal.Notify(notifCh, os.Interrupt) | |
// ждём первой записи из канала с завершающимися сигналами, как только получаем значение, можем свободно выходить. | |
<-notifCh | |
close(live) | |
} | |
// начинает парсинг по урул, запускает нужное количество горутин воркеров и возвращает канал в который пишет | |
// количество найденных на странице урлов. | |
func start(live chan struct{}, startURL string, parCnt int) chan int { | |
// создаём канал, в который мы будем пихать количество найденных урлов, и потом это дело печатать | |
urlsCount := make(chan int, 1000) | |
// а это канал для внутреннего потребления, что-то типа ограниченной очереди, сюда мы пихаем урлы для | |
// дальнейшей обработки. Здесь же пихаем и здесь же читаем. | |
urls := make(chan string, 1000) | |
// компилируем регулярку, строго 1 раз, если не компилируется паникуем нах сразу при старте, почини себе руки. | |
match := regexp.MustCompile(`href\s*=\s*"(.*?)"`) | |
// ооо функции первого класса, так для примера, и удобства именнованных возвращаемых результатов, а так же, | |
// мне надо было обязательно закрывать респонс, что-бы коннекты не утекали, при помощи отдельной функции | |
// это делать удобней и красивее. | |
read := func(r io.ReadCloser) (data []byte, err error) { | |
// прочитали всё из тела ответа, и сразу присвоили их возвращаемым значениям | |
data, err = ioutil.ReadAll(r) | |
// и закрыли сразу нахер, даже если ошибка при чтении была | |
r.Close() | |
// вернули все значения которые были выставлены во время выполнения функции. | |
return | |
} | |
// а тут не просто первокласная функция, а замыкание, наверное в этом месте все ДЖ-с скриптеры обкончились. | |
worker := func() { | |
// ну собственно при помощи ренджа, можно читать последовательно значения присылаемые в канал, | |
// если значений в канале нет, он ждёт их не выполняя цикла, как только появляется присваивает u, и выполняет | |
// цикл. | |
for u := range urls { | |
// в го одна из лучших хттп библиотек. пытаемся сделать запрос, получаем сам запрос и ошибку. | |
resp, err := http.Get(u) | |
// если ошибка не нулевая, то надо её как-то обарботать, в нашем случае печатаем и идём к след. значению. | |
if err != nil { | |
fmt.Println("Ой блять ошибка:", err) | |
continue | |
} | |
// Процесс чтения данных из запроса отделён от процесса самого запроса. Но есть одно, но, не забываем | |
// закрывать тело, после чтения, иначе может остать много открытых соединений, и хоть об этом тысячи раз | |
// сказано в документации, товичкам хоть бы что. Читайте доки суки. | |
data, err := read(resp.Body) | |
if err != nil { | |
fmt.Println("блять при чтении ошибка:", err) | |
// Та же херь в случае ошибки сообщение и начинаем цикл заново | |
continue | |
} | |
// ищем урлы, не спрашивайте почему количество -1, это типа без ограничений искать. А почему нельзя было, | |
// 0? Опять в доки суки. | |
matches := match.FindAllSubmatch(data, -1) | |
// в го можно безопасно объявлять переменные, в смысле они всегда проиницилизированны своими дефолтными | |
// значениями. | |
var found int | |
// кстати := это тоже способ создать переменну. В данном случае первое элемент это индекс, второй значение. | |
// но нам плевать на индекс, и отправляем его в пустоту, для этого есть спец переменная _. | |
for _, group := range matches { | |
// ну если ничего не нашли, пропускаем. | |
if len(group) == 0 || len(group[1]) == 0 { | |
continue | |
} | |
// не забываем что у нас строгая типизация, поэтому последовательность байтов, надо явно привести | |
// к строке, и здесь кстати аллокация памяти ещё происходит с копированием. | |
u := string(group[1]) | |
// отфильтруем мусор | |
if !strings.HasPrefix(u, "http") { | |
continue | |
} | |
// увеличиваем счётчик найденных урлов | |
found++ | |
// наряду с горутинами краегольная конструкция, эта херня выполняет первое доступное действие. | |
select { | |
// пытаемся читать из этого канала, но в него никогда не будет записано, а если его закроют, то это | |
// эквивалентно чтению, так что выполнится return, как определить, пришло значение или закрылся канал, | |
// об этом чуток надо прочитать. | |
case <-live: | |
return | |
/// вот тут обратная ситуация, так как у нас очередь, а она не резиновая в качестве неёё обычный канал | |
// с буфером на 1000 элементов, очевидно, что он очень быстро заполниться, поэтому, здесь, мы пытаемся | |
// записать в него, не получилось, так как он заполнен, ну и хер с ним, идём дальше, а ссылку игнорим. | |
case urls <- u: | |
default: | |
// ну тут очевидно если ни один из выше изложенных кейсов не был выполнен, нахер, обарбатываем | |
// следующий урл. | |
continue | |
} | |
} | |
// опа, банально отправляем количество прочитанных урлов в канал, на том конце кто-то да прочитает ))), | |
// но если канал заполнен, тоесть на той строно никто данных не выгребает, здесь мы тормазнёмся, ровно | |
// до того момента, пока в канале не появится места, тоесть, кто-то из него не прочитает. | |
urlsCount <- found | |
} | |
} | |
// банально, классическим циклом, запускаем то количество паралелльных воркеров, которое нам нужно. | |
for i := 0; i < parCnt; i++ { | |
go worker() | |
} | |
// финальный аккорд, посылаем стартовый урл в канал, урлов, что-бы один из воркеров инициировал процесс краулинга | |
// и наполнил очередь, а там и все остальные получат урлы уже от этого воркера. | |
urls <- startURL | |
// ах да, возвращаем канал считающий урыл. | |
return urlsCount | |
} | |
// печатаем статистику выгребая данные из канала статистики | |
func printStat(live chan struct{}, urlsCount chan int) { | |
// общий счётчик, видите вместо var можно вот так делать, удобнее | |
foundedURLs := 0 | |
// о классная функция, она возвращает канал, в который через указанное время придёт значение, супер вещь. | |
wait := time.After(time.Second * 2) | |
// бесконечный цикл, а хуле нам пацанам. | |
for { | |
// опять селект, кудаж без него. | |
select { | |
case <-live: | |
// закрыли канал, съёбываем из функции | |
return | |
case cnt := <-urlsCount: | |
// тут интереснее, но тоже банально, прочитали стату заинкрементили счётчик, так как делаем это только здесь, | |
// никаких мьютексов и атомарных изменений ненадо. | |
foundedURLs += cnt | |
case <-wait: | |
// оооо охренеть, в этом канале есть значение, значит прошло 2 секунды, печатаем стату. | |
fmt.Println("Найдено урлов:", foundedURLs) | |
// вежливо просим функцию ещё раз дать нам канал в который прилетит значение через 2 секунды. | |
wait = time.After(time.Second * 2) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
апишите строчку логики в Питоне как будет выглядить следующие условия : если op>70% и cl<20% И op<50% и cl>90% И cl )>hi ,что будет в какой переменной и с какого класса состоять и отпишитесь в лс t.me/Night_AngeI vk.com/hackman7872