1. Code
  2. Ruby
  3. Ruby on Rails

Construyendo su primer Web scraper , parte 2

Scroll to top

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

En este tutorial, aprenderá cómo puede utilizar Mechanize para hacer clic en vínculos, completar formularios y cargar archivos. También aprenderá cómo dividir Mechanize objetos de página y cómo automatizar una búsqueda de Google y guardar sus resultados.

Temas

  • Pagina Única vs. Paginación
  • Mechanize
  • Agente
  • Página
  • Métodos de Nokogiri
  • Enlaces
  • Click
  • Formularios

Pagina Única vs. Paginación

Hasta ahora hemos pasado algún tiempo averiguando cómo podemos raspar la pantalla de una sola página usando Nokogiri. Esta fue una buena base para avanzar un paso y aprender a extraer contenido de varias páginas.

Después de todo, el problema que estamos tratando de resolver implica obtener el contenido de más de 140 episodios, que es más contenido que puede encajar razonablemente en una sola página web. Tenemos que trabajar con la paginación y la necesidad de averiguar cómo seguir el contenido por el agujero de conejo.

Aquí es donde Nokogiri se detiene y otra joya útil llamada Mechanize entra en juego.

Mechanize

Mechanize es otra herramienta poderosa que tiene un montón de cosas que ofrecer. Esencialmente te permite automatizar las interacciones con los sitios web de los que necesitas extraer contenido. En ese sentido, me recuerda un poco de algunas funcionalidades que usted podría saber de las pruebas con Capybara.

No me malinterpretes, jugar con Nokogiri en una sola página es impresionante en sí mismo, pero para trabajos de extracción de datos más picantes, necesitamos un poco más de potencia. Esencialmente podemos rastrear las páginas que necesitamos e interactuar con sus elementos, imitando y automatizando el comportamiento humano. ¡Cosas bastante poderosas!

Esta joya le permite seguir vínculos, rellenar campos de formulario y presentar que los datos, incluso tratar con las cookies está en la mesa. Esto significa que también puede imitar el inicio de sesión de los usuarios en sesiones privadas y obtener contenido de un sitio solo al que tiene acceso.

Usted llena el inicio de sesión con sus credenciales y decirle a Mechanize cómo seguir adelante. Como puedes hacer clic en vínculos y enviar formularios, hay muy poco que no puedas hacer con esta herramienta. Tiene una estrecha relación con Nokogiri y también depende de ello. Aaron Patterson es de nuevo uno de los autores de esta preciosa joya.

Instanciar un agente de Mechanize

Antes de que podamos empezar a mecanizar las cosas, necesitamos instanciar un agente de Mechanize.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new

Este agente agent se usará para buscar una página, similar a lo que hicimos con Nokogiri.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)

Lo que sucede aquí es que el agente de Mechanize obtuvo la página de podcast y sus cookies.

Extracción del contenido de la página

Ahora tenemos una página lista para la extracción. Antes de hacerlo, recomiendo que echemos un vistazo bajo el capó usando el método de inspección inspect .

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
puts page.inspect

La salida es bastante sustantiva. Echa un vistazo y verás por ti mismo lo que consiste en un objeto Mechanize::Page. Aquí puede ver todos los atributos de esa página.Para mí, este es un objeto muy práctico para rebanar los datos que desea extraer. Aquí puede ver todos los atributos de esa página.

Para mí, este es un objeto muy práctico para rebanar los datos que desea extraer.

Salida

1
#<Mechanize::Page

2
 {url #http://betweenscreens.fm/>}

3
 {meta_refresh}
4
 {title "Between | Screens "}
5
 {iframes
6
  #<Mechanize::Page::Frame

7
   nil
8
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/290328784&color=ff0000&auto...>

9
  #<Mechanize::Page::Frame

10
   nil

11
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/290126141&color=ff0000&auto...>
12
  #<Mechanize::Page::Frame

13
   nil
14
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/289018386&color=ff0000&auto...>

15
  #<Mechanize::Page::Frame

16
   nil

17
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/287425105&color=ff0000&auto...>
18
  #<Mechanize::Page::Frame

19
   nil
20
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/287105342&color=ff0000&auto...>

21
  #<Mechanize::Page::Frame

22
   nil

23
   "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/221003494&color=ff0000&auto...>
24
  #<Mechanize::Page::Frame

25
   nil
26
   "">https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/218101809&color=ff0000&auto...}
27
 {frames}
28
 {links
29
  #<Mechanize::Page::Link "Logo cube" "/">

30
  #https://github.com/vis-kid/betweenscreens">

31
  #<Mechanize::Page::Link "about" "pages/about/">

32
  #<Mechanize::Page::Link "design" "design/">

33
  #<Mechanize::Page::Link "code" "code/">

34
  #<Mechanize::Page::Link "Randy J. Hunt" "episodes/144/">

35
  #<Mechanize::Page::Link "Jason Long" "episodes/143/">

36
  #<Mechanize::Page::Link "David Heinemeier Hansson" "episodes/142/">

37
  #<Mechanize::Page::Link "Zach Holman" "episodes/141/">

38
  #<Mechanize::Page::Link "Joel Glovier" "episodes/140/">

39
  #<Mechanize::Page::Link "João Ferreira" "episodes/139/">

40
  #<Mechanize::Page::Link "Corwin Harrell" "episodes/138/">

41
  #<Mechanize::Page::Link "Older Stuff »" "page/2/">

42
  #<Mechanize::Page::Link "Exercise" "/tags/exercise/">

43
  #<Mechanize::Page::Link "Company benefits" "/tags/company-benefits/">

44
  #<Mechanize::Page::Link "Tmux" "/tags/tmux/">

45
  #<Mechanize::Page::Link "FileTask" "/tags/filetask/">

46
  #<Mechanize::Page::Link "Decision making" "/tags/decision-making/">

47
  #<Mechanize::Page::Link "Favorite feature" "/tags/favorite-feature/">

48
  #<Mechanize::Page::Link "Working out" "/tags/working-out/">

49
  #<Mechanize::Page::Link "Scott Savarie" "/tags/scott-savarie/">

50
  #<Mechanize::Page::Link "Titles" "/tags/titles/">

51
  #<Mechanize::Page::Link "Erik Spiekermann" "/tags/erik-spiekermann/">

52
  #<Mechanize::Page::Link "Newbie mistakes" "/tags/newbie-mistakes/">

53
  #<Mechanize::Page::Link "Playbook" "/tags/playbook/">

54
  #<Mechanize::Page::Link "Delegation" "/tags/delegation/">

55
  #<Mechanize::Page::Link "Heat maps" "/tags/heat-maps/">

56
  #<Mechanize::Page::Link "Europe" "/tags/europe/">

57
  #<Mechanize::Page::Link "Sizing type" "/tags/sizing-type/">

58
  #<Mechanize::Page::Link "Focus" "/tags/focus/">

59
  #<Mechanize::Page::Link "Virtual assistants" "/tags/virtual-assistants/">

60
  #<Mechanize::Page::Link "Writing" "/tags/writing/">

61
  #<Mechanize::Page::Link "Hacking" "/tags/hacking/">

62
  #<Mechanize::Page::Link "Joel Glovier" "/tags/joel-glovier/">

63
  #<Mechanize::Page::Link "Corwin Harrell" "/tags/corwin-harrell/">

64
  #<Mechanize::Page::Link "Mario C. Delgado" "/tags/mario-c-delgado/">

65
  #<Mechanize::Page::Link "Tom Dale" "/tags/tom-dale/">

66
  #<Mechanize::Page::Link "Obie Fernandez" "/tags/obie-fernandez/">

67
  #<Mechanize::Page::Link "Chad Pytel" "/tags/chad-pytel/">

68
  #<Mechanize::Page::Link "Zach Holman" "/tags/zach-holman/">

69
  #<Mechanize::Page::Link "Max Luster" "/tags/max-luster/">

70
  #<Mechanize::Page::Link "Kyle Fiedler" "/tags/kyle-fiedler/">

71
  #<Mechanize::Page::Link "Roberto Machado" "/tags/roberto-machado/">}

72
 {forms}>

Si desea echar un vistazo a la página HTML en sí, puede marcar en el cuerpo body o métodos de contenido content .

some_scraper.rb

1
...
2
3
print page.body
4
5
...

Salida

1
<!doctype html>
2
3
<html>
4
  <head>
5
    <meta charset="utf-8" />
6
    <meta http-equiv='X-UA-Compatible' content='IE=edge;chrome=1' />
7
    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
8
    <meta name="viewport" content="initial-scale=1">
9
    <title>Between | Screens </title>
10
    <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="/feed.xml" />
11
    <link href="stylesheets/all-11b45acc.css" rel="stylesheet" />
12
    <script src="javascripts/all-4c20da82.js"></script>
13
  </head>
14
15
  <body>
16
    <header>
17
      <div id="logo">
18
        <a href="/"><img src="images/Between_Screens_Logo_Cube_Up-539d6997.svg" alt="Logo cube" /></a>
19
      </div>
20
      <nav class="navigation">
21
        <ul class="nav-list"> 
22
fork">https://github.com/vis-kid/betweenscreens">fork!
23
          <li><a href="pages/about/">about</a></li>
24
          <li><a href="design/">design</a></li>
25
          <li><a href="code/">code</a></li>
26
        </ul>
27
      </nav>
28
    </header>
29
30
    <div id="main" role="main">
31
      <div class='posts'>
32
        <ul>
33
          <li>
34
            <article class="index-article">
35
              <span class='post-date'>Oct 27 | 2016</span><h2 class='post-title'><a href="episodes/144/">Randy J. Hunt</a></h2>
36
              <h3 class='topic-list'>Organizing teams | Diversity | Desires | Pizza rule | Effective over clever | Novel solutions | Straightforwardness | Research | Coffeeshop test | Small changes | Reducing errors | Granular diffs</h3>
37
              <div class='soundcloud-player-small'>
38
                <iframe width="100%"
39
                  height="166"
40
                  scrolling="no"
41
                  frameborder="no"
42
                  src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/290328784&color=ff0000&...>

43
              </div>

44
            </article>

45
          </li>

46


47
          <li>

48
            <article class="index-article">
49
              <span class='post-date'>Oct 25 | 2016</span><h2 class='post-title'><a href="episodes/143/">Jason Long</a></h2>
50
              <h3 class='topic-list'>Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3>
51
              <div class='soundcloud-player-small'>
52
                <iframe width="100%"
53
                height="166"
54
                scrolling="no"
55
                frameborder="no"
56
                src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/290126141&color=ff0000&...>

57
              </div>

58
            </article>

59
          </li>

60


61
          <li>

62
            <article class="index-article">
63
              <span class='post-date'>Oct 18 | 2016</span><h2 class='post-title'><a href="episodes/142/">David Heinemeier Hansson</a></h2>
64
              <h3 class='topic-list'>Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk</h3>
65
              <div class='soundcloud-player-small'>
66
                <iframe width="100%"
67
                height="166"
68
                scrolling="no"
69
                frameborder="no"
70
                src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/289018386&color=ff0000&...>

71
              </div>

72
            </article>

73
          </li>

74


75
          <li>

76
            <article class="index-article">
77
              <span class='post-date'>Oct 12 | 2016</span><h2 class='post-title'><a href="episodes/141/">Zach Holman</a></h2>
78
              <h3 class='topic-list'>Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication</h3>
79
              <div class='soundcloud-player-small'>  
80
                <iframe width="100%"
81
                  height="166"
82
                  scrolling="no"
83
                  frameborder="no"
84
                  src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/287425105&color=ff0000&...>

85
              </div>

86
            </article>

87
          </li>

88


89
          <li>

90
            <article class="index-article">
91
              <span class='post-date'>Oct 10 | 2016</span><h2 class='post-title'><a href="episodes/140/">Joel Glovier</a></h2>
92
              <h3 class='topic-list'>Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source</h3>
93
              <div class='soundcloud-player-small'>
94
                <iframe width="100%"
95
                  height="166"
96
                  scrolling="no"
97
                  frameborder="no"
98
                  src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/287105342&color=ff0000&...>

99
              </div>

100
            </article>

101
          </li>

102


103
          <li>

104
            <article class="index-article">
105
              <span class='post-date'>Aug 26 | 2015</span><h2 class='post-title'><a href="episodes/139/">João Ferreira</a></h2>
106
              <h3 class='topic-list'>Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values</h3>
107
              <div class='soundcloud-player-small'>
108
                <iframe width="100%"
109
                height="166"
110
                scrolling="no"
111
                frameborder="no"
112
                src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/221003494&color=ff0000&...>

113
              </div>

114
            </article>

115
          </li>

116


117
          <li>

118
            <article class="index-article">
119
              <span class='post-date'>Aug 06 | 2015</span><h2 class='post-title'><a href="episodes/138/">Corwin Harrell</a></h2>
120
              <h3 class='topic-list'>Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays |</h3>
121
              <div class='soundcloud-player-small'>
122
                <iframe width="100%"
123
                height="166"
124
                scrolling="no"
125
                frameborder="no"
126
                src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/218101809&color=ff0000&...>

127
              </div>

128
            </article>

129
          </li>

130
        </ul>

131
      </div>

132


133
      <section>

134
        <div class='pagination-link'><a href="page/2/">Older Stuff »</a></div>
135
      </section>
136
    </div>
137
138
    <footer>
139
      <div class='footer-tags'>
140
        <h3>Random Tags</h3>
141
        <ul class='random-tag-list'>
142
          <li><a href="/tags/exercise/">Exercise</a></li>
143
          <li><a href="/tags/company-benefits/">Company benefits</a></li>
144
          <li><a href="/tags/tmux/">Tmux</a></li>
145
          <li><a href="/tags/filetask/">FileTask</a></li>
146
          <li><a href="/tags/decision-making/">Decision making</a></li>
147
          <li><a href="/tags/favorite-feature/">Favorite feature</a></li>
148
          <li><a href="/tags/working-out/">Working out</a></li>
149
          <li><a href="/tags/scott-savarie/">Scott Savarie</a></li>
150
          <li><a href="/tags/titles/">Titles</a></li>
151
          <li><a href="/tags/erik-spiekermann/">Erik Spiekermann</a></li>
152
          <li><a href="/tags/newbie-mistakes/">Newbie mistakes</a></li>
153
          <li><a href="/tags/playbook/">Playbook</a></li>
154
          <li><a href="/tags/delegation/">Delegation</a></li>
155
          <li><a href="/tags/heat-maps/">Heat maps</a></li>
156
          <li><a href="/tags/europe/">Europe</a></li>
157
          <li><a href="/tags/sizing-type/">Sizing type</a></li>
158
          <li><a href="/tags/focus/">Focus</a></li>
159
          <li><a href="/tags/virtual-assistants/">Virtual assistants</a></li>
160
          <li><a href="/tags/writing/">Writing</a></li>
161
          <li><a href="/tags/hacking/">Hacking</a></li>
162
        </ul>
163
      </div>
164
165
      <div class='recent-posts'>
166
        <h3>Random Interviewees</h3>
167
        <ul>
168
          <li><a href="/tags/joel-glovier/">Joel Glovier</a></li>
169
          <li><a href="/tags/corwin-harrell/">Corwin Harrell</a></li>
170
          <li><a href="/tags/mario-c-delgado/">Mario C. Delgado</a></li>
171
          <li><a href="/tags/tom-dale/">Tom Dale</a></li>
172
          <li><a href="/tags/obie-fernandez/">Obie Fernandez</a></li>
173
          <li><a href="/tags/chad-pytel/">Chad Pytel</a></li>
174
          <li><a href="/tags/zach-holman/">Zach Holman</a></li>
175
          <li><a href="/tags/max-luster/">Max Luster</a></li>
176
          <li><a href="/tags/kyle-fiedler/">Kyle Fiedler</a></li>
177
          <li><a href="/tags/roberto-machado/">Roberto Machado</a></li>
178
        </ul>
179
      </div>
180
    </footer>
181
  </body>
182
</html>

Dado que este podcast tiene sólo un pequeño número de elementos diferentes en la página, aquí está el Mechanize::Page que se devuelve de github.com. Tiene una mayor variedad de contenido para echar un vistazo. Creo que esto es importante para tener una idea.

Salida

1
#<Mechanize::Page

2
 {url #https://github.com/>}

3
 {meta_refresh}
4
 {title "How people build software · GitHub"}
5
 {iframes}
6
 {frames}
7
 {links
8
  #<Mechanize::Page::Link "Skip to content" "#start-of-content">

9
  #https://github.com/">

10
  #<Mechanize::Page::Link "\n          Personal\n" "/personal">

11
  #<Mechanize::Page::Link "\n          Open source\n" "/open-source">

12
  #<Mechanize::Page::Link "\n          Business\n" "/business">

13
  #<Mechanize::Page::Link "\n          Explore\n" "/explore">

14
  #<Mechanize::Page::Link "Sign up" "/join?source=header-home">

15
  #<Mechanize::Page::Link "Sign in" "/login">

16
  #<Mechanize::Page::Link "Pricing" "/pricing">

17
  #<Mechanize::Page::Link "Blog" "/blog">

18
  #https://help.github.com">

19
  #https://github.com/search">

20
  #https://help.github.com/terms">

21
  #https://help.github.com/privacy">

22
  #<Mechanize::Page::Link "Sign up for GitHub" "/join?source=button-home">

23
  #<Mechanize::Page::Link

24
   "\n      \n        \n      \n      \n        A whole new Universe\n        \n          Learn about the exciting features and announcements revealed at this year's GitHub Universe conference.\n        \n      \n    "
25
   "/universe-2016">
26
  #<Mechanize::Page::Link "Individuals " "/personal">

27
  #<Mechanize::Page::Link "Communities " "/open-source">

28
  #<Mechanize::Page::Link "Businesses " "/business">

29
  #<Mechanize::Page::Link "NASA" "//github.com/nasa">

30
  #<Mechanize::Page::Link "Sign up for GitHub" "/join?source=button-home">

31
  #https://github.com/contact">

32
  #https://developer.github.com">

33
  #https://training.github.com">

34
  #https://shop.github.com">

35
  #https://github.com/blog">

36
  #https://github.com/about">

37
  #https://github.com">

38
  #https://github.com/site/terms">

39
  #https://github.com/site/privacy">

40
  #https://github.com/security">

41
  #https://status.github.com/">

42
  #https://help.github.com">

43
  #<Mechanize::Page::Link "Reload" "">

44
  #<Mechanize::Page::Link "Reload" "">}

45
 {forms
46
  #<Mechanize::Form

47
   {name nil}
48
   {method "GET"}
49
   {action "/search"}
50
   {fields
51
    [hidden:0x3feb90f8297c type: hidden name: utf8 value: ✓]
52
    [text:0x3feb90f827d8 type: text name: q value: ]}
53
   {radiobuttons}
54
   {checkboxes}
55
   {file_uploads}
56
   {buttons}>
57
  #<Mechanize::Form

58
   {name nil}
59
   {method "POST"}
60
   {action "/join"}
61
   {fields
62
    [hidden:0x3feb90f7be38 type: hidden name: utf8 value: ✓]
63
    [hidden:0x3feb90f7bbb8 type: hidden name: authenticity_token value: vjRATKj7smXreq6Lt02r+MzW+ewWoi+fRzQXPedFAlOZgwzxQ0dZnChirhDfd7vyWZZZBO+ZFydLNedjIEDsrQ==]
64
    [text:0x3feb90f7b9d8 type: text name: user[login] value: ]
65
    [text:0x3feb90f7b7f8 type: text name: user[email] value: ]
66
    [field:0x3feb90f7b654 type: password name: user[password] value: ]
67
    [hidden:0x3feb90f7b474 type: hidden name: source value: form-home]}
68
   {radiobuttons}
69
   {checkboxes}
70
   {file_uploads}
71
   {buttons [button:0x3feb90f7a038 type: submit name:  value: ]}>}>

De nuevo al podcast, también puedes ver cosas como codificaciones, el código de respuesta HTTP, el URI o los encabezados de respuesta.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
puts 'Encodings'
10
puts page.encodings
11
puts 'Repsonse Headers'
12
puts page.response
13
puts 'HTTP response code'
14
puts page.code
15
puts 'URI'
16
puts page.uri

Salida

1
Encodings
2
EUC-JP
3
utf-8
4
utf-8
5
6
Repsonse Headers
7
{"server"=>"GitHub.com", "date"=>"Sat, 29 Oct 2016 17:56:00 GMT", "content-type"=>"text/html; charset=utf-8", "transfer-encoding"=>"chunked", "last-modified"=>"Fri, 28 Oct 2016 01:48:56 GMT", "access-control-allow-origin"=>"*", "expires"=>"Sat, 29 Oct 2016 18:06:00 GMT", "cache-control"=>"max-age=600", "content-encoding"=>"gzip", "x-github-request-id"=>"501C936D:C723:1631523C:5814E2B0"}
8
9
HTTP response code
10
200
11
12
URI
13
http://betweenscreens.fm/

Hay muchas más cosas si quieres profundizar. Lo dejaré en eso.

Métodos de Nokogiri

  • at
  • search

Mechanize utiliza Nokogiri para raspar los datos de las páginas. Usted puede aplicar lo que aprendió sobre Nokogiri en el primer artículo y utilizarlo en las páginas de Mechanize también. Eso significa que generalmente usa Mechanize para navegar páginas y métodos de Nokogiri para sus necesidades de scraping.

Por ejemplo, si desea buscar un solo objeto, puede utilizar at, mientras que la búsqueda search devuelve todos los objetos que coincidan con un selector en una página en particular. Para reformular esto, estos métodos funcionarán tanto en objetos de documento Nokogiri como en objetos de página Mechanize.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
first_title = page.at('h2.post-title')
10
11
all_titles = page.search('h2.post-title')
12
13
all_titles.each do |title|
14
  puts title
15
end
16
17
puts " * "*33
18
19
puts first_title

Salida

1
<h2 class="post-title"><a href="episodes/144/">Randy J. Hunt</a></h2>
2
<h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2>
3
<h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2>
4
<h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2>
5
<h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2>
6
<h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2>
7
<h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2>
8
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  * 
9
<h2 class="post-title"><a href="episodes/144/">Randy J. Hunt</a></h2>

Links

  • links
  • link_with
  • links_with

También podemos navegar por todo el sitio a nuestro gusto. Probablemente la parte más importante de Mechanize es su capacidad para permitirle jugar con enlaces. De lo contrario, podría bastante palo con Nokogiri por su cuenta. Echemos un vistazo a lo que nos devuelven si le pedimos una página para sus enlaces.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
puts "#{page.links}"

Salida

1
[#<Mechanize::Page::Link "Logo cube" "/">

2
, #https://github.com/vis-kid/betweenscreens">

3
, #<Mechanize::Page::Link "about" "pages/about/">

4
, #<Mechanize::Page::Link "design" "design/">

5
, #<Mechanize::Page::Link "code" "code/">

6
, #<Mechanize::Page::Link "Randy J. Hunt" "episodes/144/">

7
, #<Mechanize::Page::Link "Jason Long" "episodes/143/">

8
, #<Mechanize::Page::Link "David Heinemeier Hansson" "episodes/142/">

9
, #<Mechanize::Page::Link "Zach Holman" "episodes/141/">

10
, #<Mechanize::Page::Link "Joel Glovier" "episodes/140/">

11
, #<Mechanize::Page::Link "João Ferreira" "episodes/139/">

12
, #<Mechanize::Page::Link "Corwin Harrell" "episodes/138/">

13
, #<Mechanize::Page::Link "Older Stuff »" "page/2/">

14
, #<Mechanize::Page::Link "Exercise" "/tags/exercise/">

15
, #<Mechanize::Page::Link "Company benefits" "/tags/company-benefits/">

16
, #<Mechanize::Page::Link "Tmux" "/tags/tmux/">

17
, #<Mechanize::Page::Link "FileTask" "/tags/filetask/">

18
, #<Mechanize::Page::Link "Decision making" "/tags/decision-making/">

19
, #<Mechanize::Page::Link "Favorite feature" "/tags/favorite-feature/">

20
, #<Mechanize::Page::Link "Working out" "/tags/working-out/">

21
, #<Mechanize::Page::Link "Scott Savarie" "/tags/scott-savarie/">

22
, #<Mechanize::Page::Link "Titles" "/tags/titles/">

23
, #<Mechanize::Page::Link "Erik Spiekermann" "/tags/erik-spiekermann/">

24
, #<Mechanize::Page::Link "Newbie mistakes" "/tags/newbie-mistakes/">

25
, #<Mechanize::Page::Link "Playbook" "/tags/playbook/">

26
, #<Mechanize::Page::Link "Delegation" "/tags/delegation/">

27
, #<Mechanize::Page::Link "Heat maps" "/tags/heat-maps/">

28
, #<Mechanize::Page::Link "Europe" "/tags/europe/">

29
, #<Mechanize::Page::Link "Sizing type" "/tags/sizing-type/">

30
, #<Mechanize::Page::Link "Focus" "/tags/focus/">

31
, #<Mechanize::Page::Link "Virtual assistants" "/tags/virtual-assistants/">

32
, #<Mechanize::Page::Link "Writing" "/tags/writing/">

33
, #<Mechanize::Page::Link "Hacking" "/tags/hacking/">

34
, #<Mechanize::Page::Link "Joel Glovier" "/tags/joel-glovier/">

35
, #<Mechanize::Page::Link "Corwin Harrell" "/tags/corwin-harrell/">

36
, #<Mechanize::Page::Link "Mario C. Delgado" "/tags/mario-c-delgado/">

37
, #<Mechanize::Page::Link "Tom Dale" "/tags/tom-dale/">

38
, #<Mechanize::Page::Link "Obie Fernandez" "/tags/obie-fernandez/">

39
, #<Mechanize::Page::Link "Chad Pytel" "/tags/chad-pytel/">

40
, #<Mechanize::Page::Link "Zach Holman" "/tags/zach-holman/">

41
, #<Mechanize::Page::Link "Max Luster" "/tags/max-luster/">

42
, #<Mechanize::Page::Link "Kyle Fiedler" "/tags/kyle-fiedler/">

43
, #<Mechanize::Page::Link "Roberto Machado" "/tags/roberto-machado/">

44
]

Santo Moly, vamos a romper esto. Como no hemos dicho a Mechanize que busque en otra parte, tenemos una serie de enlaces desde esa primera página. Mechanize pasa por esa página en orden descendente y le devuelve esta lista de enlaces de arriba a abajo. He creado una pequeña imagen con punteros verdes a los varios acoplamientos que puedes ver en la salida.

Por cierto, esto ya está mostrando el resultado final del rediseño para mi podcast. Creo que esta versión es un poco mejor para fines de demostración. También se echa un vistazo a cómo se ve el resultado final y por qué necesitaba scrape mi antiguo sitio de Sinatra.

Captura de pantalla

Podcast LinksPodcast LinksPodcast Links

Como siempre, también podemos extraer sólo el texto de eso.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
page.links.each do |link|
10
  puts link.text
11
end

Salida

1
Logo cube
2
fork!
3
about
4
design
5
code
6
Randy J. Hunt
7
Jason Long
8
David Heinemeier Hansson
9
Zach Holman
10
Joel Glovier
11
João Ferreira
12
Corwin Harrell
13
Older Stuff »
14
Exercise
15
Company benefits
16
Tmux
17
FileTask
18
Decision making
19
Favorite feature
20
Working out
21
Scott Savarie
22
Titles
23
Erik Spiekermann
24
Newbie mistakes
25
Playbook
26
Delegation
27
Heat maps
28
Europe
29
Sizing type

30
Focus
31
Virtual assistants
32
Writing
33
Hacking
34
Joel Glovier
35
Corwin Harrell
36
Mario C. Delgado
37
Tom Dale
38
Obie Fernandez
39
Chad Pytel
40
Zach Holman
41
Max Luster
42
Kyle Fiedler
43
Roberto Machado

Obtener todos estos enlaces a granel puede ser muy útil o simplemente tedioso. Por suerte para nosotros, tenemos algunas herramientas en su lugar para afinar lo que necesitamos.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
focus_link = agent.page.links.find { |link| link.text == 'Focus' }
10
11
puts focus_link

Salida

1
Focus

¡Auge! ¡Ahora estamos llegando a algún sitio! Podemos hacer zoom en enlaces específicos como ese. Podemos segmentar vínculos que coincidan con ciertos criterios -como su texto, por ejemplo- con una API más agradable como links_with o link_with. Además, si tenemos múltiples vínculos de Focus, podríamos acercar un número determinado de la página usando corchetes [].

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
focus_link = agent.page.links_with(:text => 'Focus')[2]
10
11
puts focus_link

Si usted no está después del texto del enlace, pero el enlace en sí, sólo tiene que especificar un href particular para encontrar ese enlace. Mechanize no se interpondrá en su camino. En lugar de texto text, se alimentan los métodos con href

some_scraper.rb

1
page = agent.page.link_with(href: '/episodes/95/')
2
3
page = agent.page.links_with(href: '/episodes/95/')

Si sólo desea encontrar el primer enlace con el texto deseado, también puede hacer uso de esta sintaxis. Muy práctico y un poco más legible.

some_scraper.rb

1
focus_links = agent.page.link_with(:text => 'Focus')

¿Qué tal seguir a ese tipo y ver lo que se esconde detrás de este enlace de Focus? ¡Vamos a hacer clic click en él!

Click

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
focus_links = agent.page.links.find { |link| link.text == 'Focus' }.click.links
10
11
puts focus_links

Esto nos daría otra larga lista de enlaces como antes. Vea lo fácil que era combinar .click.links. Mechanize hace clic en el enlace y sigue la página al nuevo destino. Puesto que también solicitamos una lista de enlaces, obtendremos todos los enlaces que Mechanize pueda encontrar en esa nueva página.

Digamos que tengo dos enlaces de texto del mismo entrevistado-uno que enlaza con etiquetas y uno con un episodio reciente- y quiero obtener los enlaces de cada una de estas páginas.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
podcast_url = "http://betweenscreens.fm/"
6
7
page = agent.get(podcast_url)
8
9
links = agent.page.links_with(text: "Some interviewee")
10
11
links.each do |link|
12
  puts link.click.links
13
end

Esto le daría una lista de enlaces para ambas páginas. Usted itera sobre cada acoplamiento para el entrevistado, y Mechanize sigue el acoplamiento chascado y recoge los acoplamientos que encuentra en la nueva página para usted. A continuación puede encontrar algunos ejemplos donde puede comparar combinaciones para empezar.

some_scraper.rb

1
agent.page.links.find { |l| l.text == 'Focus' }
2
agent.page.links.find { |l| l.text == 'Focus' }.click
3
agent.page.link_with(text: 'Focus')
4
agent.page.links_with(text: 'Focus')[0]
5
agent.page.links_with(text: 'Focus')[1].click
6
agent.page.links_with(text: 'Focus')[2].click.links
7
agent.page.link_with(href: '/some-href')
8
agent.page.link_with(href: '/some-href').click
9
agent.page.links_with(href: '/some-href')
10
agent.page.links_with(href: '/some-href').click

Formularios

  • submit
  • field_with
  • checkbox_with
  • radiobuttons_with
  • file_uploads

¡Echemos un vistazo a los formularios!

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
google_url = "http://google.com/"
6
7
page = agent.get(google_url)
8
9
forms = page.forms
10
11
puts forms.inspect

Salida

1
[#<Mechanize::Form

2
# Attention!!

3
 {name "f"}
4
# Attention!!

5
 {method "GET"}
6
 {action "/search"}
7
 {fields
8
  [hidden:0x3fea91d2eb08 type: hidden name: ie value: ISO-8859-1]
9
  [hidden:0x3fea91d2e964 type: hidden name: hl value: es]
10
  [hidden:0x3fea91d2e7e8 type: hidden name: source value: hp]
11
  [hidden:0x3fea91d2e5f4 type: hidden name: biw value: ]
12
  [hidden:0x3fea91d2e428 type: hidden name: bih value: ]
13
# Attention!!

14
  [text:0x3fea91d2e248 type:  name: q value: ]
15
# Attention!!

16
  [hidden:0x3fea91d2bcb4 type: hidden name: gbv value: 1]}
17
 {radiobuttons}
18
 {checkboxes}
19
 {file_uploads}
20
 {buttons
21
  [submit:0x3fea91d2e0f4 type: submit name: btnG value: Buscar con Google]
22
  [submit:0x3fea91d2be80 type: submit name: btnI value: Voy a tener suerte]}>
23
]

Debido a que usamos el método de formularios forms, obtenemos una matriz devuelta, incluso cuando sólo tenemos un formulario devuelto a nosotros. Ahora que sabemos que la forma tiene el nombre "f", podemos usar la forma de versión singular form para afinar en que uno.

1
...
2
3
{name "f"}
4
5
...

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
google_url = "http://google.com/"
6
7
page = agent.get(google_url)
8
9
search_form = page.form('f')
10
11
puts search_form.inspect

Usando el form('f'), señalamos la forma particular con la que queremos trabajar. Como resultado, no obtendremos una matriz devuelta.

Salida

1
#<Mechanize::Form

2
# Attention!!

3
 {name "f"}
4
# Attention!!

5
 {method "GET"}
6
 {action "/search"}
7
 {fields
8
  [hidden:0x3ffe9ce85ba4 type: hidden name: ie value: ISO-8859-1]
9
  [hidden:0x3ffe9ce859d8 type: hidden name: hl value: es]
10
  [hidden:0x3ffe9ce857bc type: hidden name: source value: hp]
11
  [hidden:0x3ffe9ce85618 type: hidden name: biw value: ]
12
  [hidden:0x3ffe9ce853e8 type: hidden name: bih value: ]
13
# Attention!!

14
  [text:0x3ffe9ce851cc type:  name: q value: ]
15
# Attention!!

16
  [hidden:0x3ffe9ce84bdc type: hidden name: gbv value: 1]}
17
 {radiobuttons}
18
 {checkboxes}
19
 {file_uploads}
20
 {buttons
21
  [submit:0x3ffe9ce85078 type: submit name: btnG value: Buscar con Google]
22
  [submit:0x3ffe9ce84e48 type: submit name: btnI value: Voy a tener suerte]}>

También podemos identificar el nombre del campo de entrada de texto (q).

1
...
2
3
[text:0x3ffe9ce851cc type:  name: q value: ]
4
5
...

Podemos apuntarla por ese nombre y establecer su valor como atributos Ruby. Todo lo que necesitamos hacer es proporcionarle un nuevo valor. Puede ver en el ejemplo de salida anterior que está vacía por defecto.

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
google_url = "http://google.com/"
6
7
page = agent.get(google_url)
8
9
search_form = page.form('f')
10
search_form.q = 'New Google Search'
11
12
puts search_form.inspect

Salida

1
#<Mechanize::Form

2
 {name "f"}
3
 {method "GET"}
4
 {action "/search"}
5
 {fields
6
  [hidden:0x3fcb85b6a784 type: hidden name: ie value: ISO-8859-1]
7
  [hidden:0x3fcb85b6a57c type: hidden name: hl value: es]
8
  [hidden:0x3fcb85b6a3b0 type: hidden name: source value: hp]
9
  [hidden:0x3fcb85b6a16c type: hidden name: biw value: ]
10
  [hidden:0x3fcb85b67f20 type: hidden name: bih value: ]
11
# Attention!!

12
  [text:0x3fcb85b67d18 type:  name: q value: New Google Search]
13
# Attention!!

14
  [hidden:0x3fcb85b67728 type: hidden name: gbv value: 1]}
15
 {radiobuttons}
16
 {checkboxes}
17
 {file_uploads}
18
 {buttons
19
  [submit:0x3fcb85b67b9c type: submit name: btnG value: Buscar con Google]
20
  [submit:0x3fcb85b67994 type: submit name: btnI value: Voy a tener suerte]}>

Como se puede observar anteriormente, el valor del campo de texto ha cambiado a Nueva búsqueda de Google New Google Search Ahora solo necesitamos enviar el formulario submit y recopilar los resultados de la página que Google devuelve. No podría ser más fácil. ¡Busquemos algo más esta vez!

some_scraper.rb

1
require 'mechanize'
2
3
agent = Mechanize.new
4
5
google_url = "http://google.com/"
6
page = agent.get(google_url)
7
8
search_form = page.form('f')
9
search_form.q = 'GitHub TouchFart'
10
11
page = agent.submit(search_form)
12
13
pp page.search('h3.r').map(&:text)

Aquí identifiqué el encabezado de los resultados de búsqueda usando un selector de CSS h3.r, mapeado su texto text, y bastante impreso los resultados. ¿No fue tan difícil, verdad? Ese es un ejemplo fácil, claro, pero ¡piensa en las infinitas posibilidades que tienes a tu disposición con esto!

Salida

1
["GitHub - hungtruong/TouchFart: A fart app for the new Macbook ...",
2
 "TouchFart/TouchFart at master · hungtruong/TouchFart · GitHub",
3
 "Commits · hungtruong/TouchFart · GitHub",
4
 "Projects · hungtruong/TouchFart · GitHub",
5
 "Pull Requests · hungtruong/TouchFart · GitHub",
6
 "Issues · hungtruong/TouchFart · GitHub",
7
 "TouchFart/license.txt at master · hungtruong/TouchFart · GitHub",
8
 "Add autoplay attribute to <audio> tag and touchfart (er ... - GitHub",
9
 "Find file - File Finder · GitHub",
10
 "Fart app for the new Macbook Pro's Touch... #3860 on topic touchfart ..."]

Mechanize tiene diferentes campos de entrada disponibles para jugar. ¡Incluso puede subir archivos!

  • field_with
  • checkbox_with
  • radiobuttons_with
  • file_uploads

También puede identificar los botones de radio y las casillas de verificación por su nombre y comprobarlos con—lo has adivinado—check.

some_scraper.rb

1
form.radiobuttons_with(:name => 'gender')[3].check
2
3
form.checkbox_with(:name => 'coder').check

Las etiquetas de opción ofrecen a los usuarios seleccionar un elemento de una lista desplegable. Nuevamente, los seleccionamos por nombre y seleccionamos el número de opción que deseamos.

some_scraper.rb

1
form.field_with(:name => 'countries').options[22].select

Las subidas de archivos funcionan de forma similar a la introducción de texto en los formularios al establecerlo como atributos de Ruby. Identifica el campo de carga y luego especifica la ruta del archivo (nombre del archivo) que deseas transferir. Suena más complicado de lo que es. ¡Echemos un vistazo!

some_scraper.rb

1
form.file_uploads.first.file_name = "some-path/some-image.jpg"

Pensamientos finales

Ves, ¡no hay magia después de todo! Ahora está bien equipado para divertirse por su cuenta. Sin duda hay un poco más para aprender acerca de Nokogiri y Mechanize, pero en lugar de pasar demasiado tiempo en aspectos innecesarios, jugar con él y buscar en más documentación cuando se encuentra con problemas más allá del alcance de un artículo para principiantes.

Espero que usted pueda ver cómo maravillosamente simple esta gema es y cuánto energía ofrece. Como todos sabemos de la cultura popular por ahora, esto también tiene responsabilidades. Úselo dentro de los marcos legales y cuando no tenga acceso a una API. Usted probablemente no tendrá un uso frecuente para estas herramientas, pero el muchacho no vienen en práctico cuando usted tiene algunas necesidades que raspan verdaderas delante de usted.

Como se prometió, en el próximo artículo cubriremos un ejemplo del mundo real donde rasparé datos de mi sitio de podcast. Lo extraeré de un antiguo sitio de Sinatra y lo transferiré a mi nuevo sitio de Middleman que usa archivos .markdown para cada episodio. Extraeremos las fechas, los números de los episodios, los nombres de los entrevistados, los encabezados, los subtítulos, etc. ¡Te veo allí!