Een website schrapen met Nokogiri

I am using Nokogiri to scrape a website and am running into an issue when I try to grab a field from a table. I am using selector gadget to find the CSS selector of the table. I am grabbing data from a government website that details information on motor carriers.

De methode die ik gebruik ziet er als volgt uit:

def scrape_database
  url = "http://safer.fmcsa.dot.gov/query.asp?searchtype=ANY&query_type=queryCarrierSnapshot&query_param=USDOT&query_string=#{self.dot}#Inspections"
  doc = Nokogiri::HTML(open(url))
  self.name = doc.at_css("tr:nth-child(4) .queryfield").text
  self.address = doc.at_css("tr:nth-child(6) .queryfield").text
end

Ik pak alle velden in de bovenste tabel met die syntaxis en de methode werkt prima, maar ik heb problemen met de tabel met crash rates/inspecties eronder.

Dit is wat ik gebruik om die info te pakken:

self.vehicle_inspections = doc.at_css("center:nth-child(13) tr:nth-child(2) :nth-child(2)").text

undefined method `text' for nil:NilClass

Als ik tekst aan het einde hiervan verwijder, wordt de methode uitgevoerd, maar deze haalt geen relevante informatie (uiteraard). Ik ga ervan uit dat dit komt door de gecompliceerde selector die ik gebruik om het veld te pakken, maar ik ben er niet helemaal zeker van.

Heeft iemand een soortgelijk probleem tegengekomen en kun je me wat advies geven?

2
Voeg wat voorbeeld-HTML toe die het probleem aantoont. Als de pagina waarnaar de URL verwijst, zou verdwijnen, zou uw vraag niet echt iemand helpen die het probleem in de toekomst zou tegenkomen.
toegevoegd de auteur the Tin Man, de bron

1 antwoord

Ja, die fout betekent dat uw CSS-kiezer de informatie niet vindt; at_css retourneert nul en nil.text is niet geldig. Je kunt er als volgt tegen waken:

insp = doc.at_css("long example CSS selector")
self.vehicle_inspections = insp && insp.text

Het klinkt mij echter alsof u deze gegevens "nodig" hebt. Omdat u de HTML-pagina en de CSS-kiezers niet hebt opgegeven, kan ik u niet helpen een werkende CSS- of XPath-selector te maken.

Let er bij toekomstige vragen, of een bewerking op deze, op dat de werkelijke (verlaagde) code sterk de voorkeur heeft boven handbewegingen en losse beschrijvingen van hoe uw code eruitziet. Als u ons de HTML-pagina of een relevant fragment laat zien en beschrijft welk element/tekst/kenmerk u wilt, kunnen we u vertellen hoe u dit moet selecteren.

Update: I see 6 tables on that page. Which is the "crash rate/inspections" table? Given that your URL includes #Inspections on the end, I'm assuming you're talking about the two tables immediately underneath the "Inspections/Crashes In US" section. Here are XPath selectors that match each:

require 'nokogiri'
require 'open-uri'

url = "http://safer.fmcsa.dot.gov/query.asp?searchtype=ANY&query_type=queryCarrierSnapshot&query_param=USDOT&query_string=800585"
doc = Nokogiri::HTML(open(url))
table1 = doc.at_xpath('//table[@summary="Inspections"][preceding::h4[.//a[@name="Inspections"]]]')
table2 = doc.at_xpath('//table[@summary="Crashes"][preceding::h4[.//a[@name="Inspections"]]]')

# Find a row by index (1 is the first row)
vehicle_inspections    = table1.at_xpath('.//tr[2]/td').text.to_i

# Find a row by header text
out_of_service_drivers = table1.at_xpath('.//tr[th="Out of Service"]/td[2]').text.to_i

p [ vehicle_inspections, out_of_service_drivers ]
#=> [6, 0]

tow_crashes = table2.at_xpath('.//tr[th="Crashes"]/td[3]').text.to_i
p tow_crashes
#=> 0

De XPath-query's kunnen intimiderend lijken. Laat me uitleggen hoe ze werken:

  1. //table[@summary="Inspections"][preceding::h4[.//a[@name="Inspections"]]]

    This would actually match two tables (there's another summary="Inspections" table later on the page), but using at_xpath finds the first matching table.

  2. .//tr[2]/td

    • . Starting at the current node (this table)
    • //tr[2] …find the second <tr> that is a descendant at any level
    • /td …and then find the <td> children of that.

    Again, because we're using at_xpath we find the first matching <td>.

  3. .//tr[th="Out of Service"]/td[2]

    • . Starting at the current node (this table)
    • //tr …find any <tr> that is a descendant at any level
      • [th="Out of Service] …but only those <tr> that have a <th> child with this text
    • /td[2] …and then find the second <td> children of those.

    In this case there is only one <tr> that matches the criteria, and thus only one <td> that matches, but we still use at_xpath so that we get that node directly instead of a NodeSet with a single element in it.

The goal here (and with any screen scraping) is to latch onto meaningful values on the page, not arbitrary indices.

For example, I could have written my table1 xpath as:

# Find the first table with this summary
table1 = doc.at_xpath('//table[@summary="Inspections"][1]')

…or even…

# Find the 20th table on the page
//table[20]

However, those are fragile. Someone adding a new section to the page, or code that happens to add or remove a formatting table would cause those expressions to break. You want to hunt for strong attributes and text that likely won't change, and anchor your searches based on that.

The vehicle_inspections XPath is similarly fragile, relying on the ordering of rows instead of the label text for the row.

@ demondeac11 Geweldig, dat helpt. Ik heb mijn antwoord bewerkt om te krijgen wat ik denk dat je wilt, legde uit hoe het XPath werkt, zodat je misschien je eigen query's kunt maken en probeerde te beschrijven waarom het gebruik van CSS-kiezers op basis van indices te kwetsbaar is .
toegevoegd de auteur Phrogz, de bron
Dit is de voorbeeldpagina waarvan ik probeer informatie te halen: veiliger Er zijn twee hoofdtabellen met gegevens die ik bekijk, de persoonlijke gegevens en de tabel met inspecties/crashes eronder. Ik zal de selectors hierboven bijwerken om te laten zien wat ik gebruik en wat niet werkt.
toegevoegd de auteur tomciopp, de bron
Dit was eerlijk gezegd het beste antwoord dat ik ooit heb gekregen op stack overflow. Dit is een probleem dat me al een tijdje in de maling neemt, en ik ben zo blij dat je me niet alleen hebt laten zien hoe je het oplost, maar ook hoe je het kunt gebruiken via xpath, zodat ik toekomstige problemen alleen kan oplossen.
toegevoegd de auteur tomciopp, de bron
@Phrogz +1 Bedankt, je uitleg heeft me enorm geholpen
toegevoegd de auteur Hishalv, de bron