Jag älskar webbskrapning. Det har inte bara blivit en stor industri de senaste åren, det är också mycket roligt. Det kan hjälpa dig att enkelt hitta intressanta insikter med hjälp av data som redan finns. Idag ska jag bygga en mycket grundläggande webbskrapa som kan söka igenom DuPont Registry (en marknadsplats för dyra bilar, hem och yachter); för denna skrapa kommer jag att fokusera på bildelen.
Jag kommer att använda två relevanta pärlor: Nokogiri och HTTParty . Nokogiri är en pärla som låter dig analysera HTML och XML i Ruby -objekt. HTTParty, å andra sidan, förenklar processen för att dra rå HTML till din Ruby -kod. Dessa två pärlor fungerar tillsammans i vår skrapa.
Det är viktigt att notera att även om Nokogiri och HTTParty är coola, är de mer som strössel ovanpå. Kärnkompetensen för att bygga ett program med detta är saker som att definiera och initiera en klass och dess instans (er), iterera genom hascher och matriser och bygga hjälpmetoder.
Komma igång
Om du inte redan har gjort det, se till att du har dina viktiga pärlor installerade och krävs i din kod. Jag gillar också att använda byebug eller bända så att jag kan stoppa koden om det behövs och titta på vad som händer!
require 'nokogiri' require 'httparty' require 'byebug' require 'pry' class Scraper #we will be adding code here shortly end
Initierar en instans
Låt oss nu tänka på vår Scraper -klass. Vad ska den ha för metoder? Vad behöver den här klassen för att hålla reda på?
Vi måste bestämma detta innan vi definierar hur vår nya skrapa initieras. Jag har redan funderat lite på detta, och här är vad jag kom på:
class Scraper attr_reader :url, :make, :model def initialize(make, model) @make = make.capitalize @model = model.capitalize @url = 'https://www.dupontregistry.com/autos/results/#{make}/#{model}/for-sale'.sub(' ', '--') end end
initiera. rb
hur man skriver ut pi i python
Låt oss bryta ner det här. Varje instans av en Scraper -klass borde veta vad göra och modell den ska titta på och vilken URL den måste besöka för att hitta sin data. Det skulle också vara trevligt om dessa instansvariabler var läsbara, så jag har lagt till en attr_reader till var och en av dem.
DuPont Registry URL -sökvägar är ganska okomplicerade, så vi kan dra in mallbokstavarna från 'make' och 'model' för att generera vår destinationsadress och sedan spara den i vår instansvariabel av @url . De .sub (, -) i slutet av URL -strängen är bara en metod som ersätter blanksteg med två bindestreck.
Dags att lägga till lite mer funktionalitet. Vi behöver vår skrapainstans för att faktiskt, ja, skrapa. Låt oss börja bygga ut några instansmetoder.
class Scraper attr_reader :url, :make, :model def initialize(make, model) @make = make.capitalize @model = model.capitalize @url = 'https://www.dupontregistry.com/autos/results/#{make}/#{model}/for-sale'.sub(' ', '--') end def parse_url(url) unparsed_page = HTTParty.get(url) Nokogiri::HTML(unparsed_page) end def scrape parsed_page = parse_url(@url) binding.pry end binding.pry 0 end
gistfile1.rb
Jag vill komma igång med dessa hjälpmetoder tidigt före vår #skrapa metoden blir en roman. De #parse_url metod tar in en URL som ett argument, anropar HTTParty att dra in rå HTML, och sedan Nokogiri tar den oparserade sidan och ... umm ... analyserar det . Precis så kan vi ta en hel webbsida och förvandla den till ett användbart Ruby -objekt! Vi sparar sedan hela objektet i en lokal variabel inuti #scrape som kallas parsed_page. Nu är en ganska bra tid att ställa in en bindning. Prata och ta en titt på vad vi faktiskt har innan vi går vidare.
Låt oss fortsätta och köra den här koden i vår terminal med bash -kommandot rubinskrapa.rb (detta förutsätter att du kodar i en fil med titeln scraper.rb, om din fil har ett annat namn, justera därefter). Se till att du är i rätt katalog.
Nu är du (eller borde vara) i en bända session, så vi kan börja skriva Ruby -kod i vår terminal. Låt oss först skapa en ny Scraper -instans:
bentley = Scraper.new('bentley', 'continental GT')
Coolt, vi har en ny instans av en Scraper som kommer att leta efter Bentley Continental GT på https://www.dupontregistry.com/autos/results/bentley/continental–gt/for-sale . Vi kan bekräfta detta genom att skriva följande i vår terminal:
bentley.url #=> 'https://www.dupontregistry.com/autos/results/bentley/continental--gt/for-sale'
Bra! Låt oss nu komma igång. Vi måste anropa vår #skrapa -metod på vår Scraper -instans av bentley så att vi kan träffa vår andra bindning. pry och ta en titt på vad vi fick:
bentley.scrape
Det jag är intresserad av just nu är vad exakt parsed_page är eftersom det är mycket Ruby -pärlbrott på gång. Låt oss ringa till parsed_page lokal variabel.
pry(#)> parsed_page
Oj! Okej. Det är många saker. Det är klart att det gör något, jag är bara inte säker på vad jag ska göra med allt detta än. Min såg ut ungefär så här:
var man kan köpa satsmynt
Detta kan se annorlunda ut om du skrapar en annan webbplats.
Innan vi går för djupt in i att försöka ta reda på hur vi ska skrapa våra bitar av data från detta enorma objekt, låt oss titta på vad jag försöker få till så att vi kan omvandla det. Jag vill ha en hash som ser ut ungefär så här:
{ year: 2007, name: 'Bentley', model: 'Continental GT', price: 150000, link: 'https://www.dupontregistry.com/link_to_my_car' }
hash.rb
Häftigt, så istället för att försöka ta ut varje data, letar vi bara efter dessa fem data: år, märke, modell, pris och länk. Men vänta! Vi känner redan till vårt märke och modell från när vi instanserade vår skrapinstans, så vi letar egentligen bara efter 3 datapunkter.
Hanterar HTML
Nu när vi vet vad vi letar efter, låt oss titta igenom HTML -koden för sidan vi vill skrapa för att hitta våra vägar. Låt oss först se om vi kan hitta behållaren som innehåller all vår relevanta bilinformation. Vi måste besöka listans sida och öppna utvecklarverktyg.
Låt oss spara varje lista i en variabel som vi kan använda senare. Vi använder .css -metoden från Nokogiri för att bygga denna array.
cars = parsed_page.css('div.searchResults') #creates an array where each element is parsed HTML pertaining to a different listing
Nu ska vi bara försöka hitta priset.
Vi måste leka med det här för att få fram den text vi behöver. Det är bäst att göra detta i en pry eller byebug -session. Jag kunde dra ut priset med följande kod:
car.css('.cost').children[1].text.sub(',','').to_i #looks in an instance of car for the cost div #looks in its children at an index of 1 (this is where it lives) #converts it to text #gets rid of the comma #converts the string into an integer
Vi har pris, nu får vi ett år. På samma sätt måste vi peta runt med utvecklarverktyg för att hitta vår sträng som innehåller bilens år. I mitt fall slutade detta med att vara en sträng som såg ut som 2017 Bentley Continental GT V8 S så jag bestämde mig för att bara stjäla de fyra första tecknen och konvertera det till ett heltal:
car.css('a').children[0].text[0..4].strip.to_i #looks in an instance of car for the tag #looks in its children at an index of 0 (this is where it lives) #converts to text #takes the first 4 characters (ie '2017') #strips any whitespace if it exists #converts to integer
Sist men inte minst, låt oss få vår länk till bilens sida.
car.css('a').attr('href').value #looks in an instance of car for the tag #gets the attribute of #pulls out is value (as a string)
Så, vi har alla våra JQuery-esque grejer räknade ut. När du läser vidare ser du hur dessa implementeras i hjälpmetoden skapa_bil_hash .
var man kan köpa terra mynt
Slutför skrapan
Härifrån är det ganska enkelt att göra klart vår skrapa. Låt oss sätta upp våra hjälpmetoder så att vår #skrapmetod kommer att ha allt den behöver.
def create_car_hash(car_obj) #creates a hash with the values we need from our parsed car object car_obj.map { |car| { year: car.css('a').children[0].text[0..4].strip.to_i, name: @make, model: @model, price: car.css('.cost').children[1].text.sub(',','').to_i, link: 'https://www.dupontregistry.com/#{car.css('a').attr('href').value}' } } end def get_all_page_urls(array_of_ints) #gets URLs of all pages, not just the first page array_of_ints.map { |number| @url + '/pagenum=#{number}' } end def get_number_of_pages(listings, cars_per_page) #finds how many pages of listings exist a = listings % cars_per_page if a == 0 listings / cars_per_page else listings / cars_per_page + 1 end end def build_full_cars(number_of_pages) #builds an array of car hashes for each page of listings, starting on page 2 a = [*2..number_of_pages] all_page_urls = get_all_page_urls(a) all_page_urls.map url end
helper_methods.rb
Jag gjorde fyra olika hjälpmetoder:
- skapa_bil_hash skapar en hash baserad på de värden vi behöver
- get_all_page_urls gör som det låter! Samlar alla webbadresser till en matris som gör att vi kan ta in paginering
- get_number_of_pages är ganska självförklarande - det hjälper oss också att hantera paginering
- build_full_cars ger oss en rad bilhashar från sidan 2 och senare
Låt oss sätta ihop allt i #skrapmetoden så att vi kan få vad vi vill. I slutet ser min kod ut så här:
require 'nokogiri' require 'rest-client' require 'httparty' require 'byebug' require 'pry' class Scraper attr_reader :url, :make, :model def initialize (make, model) @make = make.capitalize @model = model.capitalize @url = 'https://www.dupontregistry.com/autos/results/#{make}/#{model}/for-sale'.sub(' ', '--') end def parse_url(url) unparsed_page = HTTParty.get(url) Nokogiri::HTML(unparsed_page) end def scrape parsed_page = parse_url(@url) cars = parsed_page.css('div.searchResults') #Nokogiri object containing all cars on a given page per_page = cars.count #counts the number of cars on each page, should be 10 total_listings = parsed_page.css('#mainContentPlaceholder_vehicleCountWithin').text.to_i total_pages = self.get_number_of_pages(total_listings, per_page) first_page = create_car_hash(cars) all_other = build_full_cars(total_pages) first_page + all_other.flatten end def create_car_hash(car_obj) car_obj.map { |car| { year: car.css('a').children[0].text[0..4].strip.to_i, name: @make, model: @model, price: car.css('.cost').children[1].text.sub(',','').to_i, link: 'https://www.dupontregistry.com/#{car.css('a').attr('href').value}' } } end def get_all_page_urls(array_of_ints) array_of_ints.map { |number| @url + '/pagenum=#{number}' } end def get_number_of_pages(listings, cars_per_page) a = listings % cars_per_page if a == 0 listings / cars_per_page else listings / cars_per_page + 1 end end def build_full_cars(number_of_pages) a = [*2..number_of_pages] all_page_urls = get_all_page_urls(a) all_page_urls.map pu = parse_url(url) cars = pu.css('div.searchResults') create_car_hash(cars) end end
DuP.rb
Det kan tyckas lite konstigt att bygga våra hashningar för den första sidan och resten av sidorna separat (se rad 32), och du kan förmodligen konsolidera detta, men den första sidans URL är lite annorlunda och är viktig för att få vår initial datauppsättning.
Låt oss köra det här snabbt för att se till att det gör vad vi vill att det ska göra.
ruby scraper.rb bentley = Scraper.new('bentley', 'continental gt') bentley.scrape
Vår produktion är enorm! Jag var tvungen att förkorta det lite. Detta är vad jag fick:
[{:year=>2017, :name=>'Bentley', :model=>'Continental gt', :price=>179910, :link=>'https://www.dupontregistry.com//autos/listing/2017/bentley/continental--gt/2080775'}, {:year=>2012, :name=>'Bentley', :model=>'Continental gt', :price=>86200, :link=>'https://www.dupontregistry.com//autos/listing/2012/bentley/continental--gt/2070330'}, {:year=>2016, :name=>'Bentley', :model=>'Continental gt', :price=>135988, :link=>'https://www.dupontregistry.com//autos/listing/2016/bentley/continental--gt/2077824'}, {:year=>2016, :name=>'Bentley', :model=>'Continental gt', :price=>139989, :link=>'https://www.dupontregistry.com//autos/listing/2016/bentley/continental--gt/2086794'}, {:year=>2016, :name=>'Bentley', :model=>'Continental gt', :price=>0, :link=>'https://www.dupontregistry.com//autos/listing/2016/bentley/continental--gt--speed/2086825'}, {:year=>2015, :name=>'Bentley', :model=>'Continental gt', :price=>119888, :link=>'https://www.dupontregistry.com//autos/listing/2015/bentley/continental--gt/2086890'}, ...}]
output_hash.rb
Om vi kallar .length på denna array får vi verkligen 120, vilket matchar vårt antal listor. Perfekt!
Det finns fortfarande många metoder vi skulle vilja bygga in i detta för att göra det användbart. Till exempel kan vi skapa en metod som returnerar länken till den billigaste Bentley som för närvarande är listad, eller en som beräknar det genomsnittliga begärda priset. Du kan till och med försköna den här koden ganska mycket eftersom den är långt ifrån perfekt. Jag kommer dock att stanna här för idag innan den här artikeln blir Krig och fred ! Här är en länk till GitHub -förvaret om du vill använda den som inspiration för din egen webbskrapa.
#ruby #Scraper #programmering
itnext.io
Bygga en grundskrapa med Ruby
Bygga en grundskrapa med Ruby. Jag älskar webbskrapning. Det har inte bara blivit en stor industri de senaste åren, det är också mycket roligt. Det kan hjälpa dig att enkelt hitta intressanta insikter med hjälp av data som redan finns.