-
-
Save drogus/0b11aa2b26c7dc0bd7fa833b45649ca2 to your computer and use it in GitHub Desktop.
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
[dependencies] | |
futures-preview = { version = "0.3.0-alpha.11", features = ["tokio-compat","compat"] } | |
futures-util-preview = "0.3.0-alpha.11" | |
hyper = "0.12" | |
hyper-tls = "0.3" | |
tokio = "0.1" | |
serde = "1.0" | |
serde_derive = "1.0" | |
serde_json = "1.0" | |
select = "0.4.2" | |
decimal = "2.0.4" |
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
#![feature(futures_api, async_await, await_macro)] | |
extern crate futures; | |
extern crate futures_util; | |
extern crate hyper; | |
extern crate hyper_tls; | |
extern crate select; | |
extern crate serde; | |
extern crate serde_json; | |
extern crate tokio; | |
use decimal::d128; | |
use futures::compat::Future01CompatExt; | |
use futures::{FutureExt, TryFutureExt}; | |
use futures_util::future::join_all; | |
use hyper::rt::Stream; | |
use hyper::{Body, Client, Request}; | |
use hyper_tls::HttpsConnector; | |
use select::document::Document; | |
use select::predicate::{Attr, Name, Predicate}; | |
use std::error::Error; | |
use std::fmt; | |
use std::io::Cursor; | |
use std::num; | |
#[derive(Debug)] | |
struct Location(f32, f32); | |
#[derive(Debug)] | |
struct Auction { | |
id: u64, | |
title: String, | |
current_price: d128, | |
location: Location, | |
end_date: String, // TODO: I think it would be nice to use a library or create a struct for keeping type | |
} | |
#[derive(Debug)] | |
enum APError { | |
Request(hyper::Error), | |
Io(std::io::Error), | |
Parse(num::ParseIntError), | |
PageNotFound(String), | |
AttributeNotFound(String), | |
} | |
impl From<hyper::Error> for APError { | |
fn from(err: hyper::Error) -> APError { | |
APError::Request(err) | |
} | |
} | |
impl From<num::ParseIntError> for APError { | |
fn from(err: num::ParseIntError) -> APError { | |
APError::Parse(err) | |
} | |
} | |
impl From<std::io::Error> for APError { | |
fn from(err: std::io::Error) -> APError { | |
APError::Io(err) | |
} | |
} | |
impl fmt::Display for APError { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
match *self { | |
APError::Io(ref err) => err.fmt(f), | |
APError::Request(ref err) => err.fmt(f), | |
APError::Parse(ref err) => err.fmt(f), | |
APError::PageNotFound(ref url) => write!(f, "Page not found for url {}", url), | |
APError::AttributeNotFound(ref attr) => write!(f, "Attribute not found {}", attr), | |
} | |
} | |
} | |
impl Error for APError { | |
fn description(&self) -> &str { | |
match *self { | |
APError::Io(ref err) => err.description(), | |
APError::Request(ref err) => err.description(), | |
APError::Parse(ref err) => err.description(), | |
APError::PageNotFound(ref _url) => "Page not found", | |
APError::AttributeNotFound(ref _attr) => "Attribute not found", | |
} | |
} | |
} | |
async fn fetch_auction_ids( | |
client: &Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>, | |
url: String, | |
) -> Result<Vec<u64>, APError> { | |
let mut ids: Vec<u64> = Vec::new(); | |
let req = Request::get(&url) | |
.header("User-Agent", "hyper/0.12") | |
.body(Body::empty()) | |
.unwrap(); | |
let response = await!(client.request(req).compat())?; | |
if response.status() != 200 { | |
return Err(APError::PageNotFound(url)); | |
} | |
let body = await!(response.into_body().concat2().compat())?; | |
let document = Document::from_read(Cursor::new(body))?; | |
for node in document.find( | |
Attr("id", "ResultSetItems") | |
.child(Attr("id", "ListViewInner")) | |
.child(Name("li")), | |
) { | |
let id = node | |
.attr("listingid") | |
.and_then(|str| Some(str.parse::<u64>().unwrap_or(0))) | |
.unwrap_or(0); | |
if id > 0 { | |
ids.push(id); | |
} | |
} | |
Ok(ids) | |
} | |
async fn fetch_auction_data( | |
client: &Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>, | |
id: u64, | |
) -> Result<Auction, APError> { | |
let url = format!("https://www.ebay.de/itm/{}", id); | |
let req = Request::get(&url) | |
.header("User-Agent", "hyper/0.12") | |
.body(Body::empty()) | |
.unwrap(); | |
let response = await!(client.request(req).compat())?; | |
if response.status() != 200 { | |
return Err(APError::PageNotFound(url)); | |
} | |
let body = await!(response.into_body().concat2().compat())?; | |
let document = Document::from_read(Cursor::new(body))?; | |
let title = match document.find(Attr("id", "itemTitle")).into_iter().next() { | |
Some(title_node) => title_node.text(), | |
None => return Err(APError::AttributeNotFound("title".to_string())) | |
}; | |
let auction = Auction { | |
id: 1, | |
title: title, | |
current_price: d128!(11.1), | |
end_date: "2018-05-10".to_string(), | |
location: Location(12.33, 11.11), | |
}; | |
return Ok(auction); | |
} | |
async fn fetch(url: String) { | |
let https = HttpsConnector::new(4).unwrap(); | |
let client = Client::builder().build::<_, Body>(https); | |
let result = await!(fetch_auction_ids(&client, url)); | |
match result { | |
Ok(ids) => { | |
let mut futures = Vec::new(); | |
for id in ids { | |
futures.push(fetch_auction_data(&client, id).boxed()); | |
} | |
let results = await!(join_all(futures)); | |
for result in results { | |
match(result) { | |
Ok(auction) => println!("{}", auction.title), | |
Err(e) => eprintln!("{}", e) | |
} | |
} | |
} | |
Err(e) => eprintln!("{}", e), | |
} | |
} | |
fn main() { | |
// do it async | |
let url = "https://www.ebay.de/sch/i.html?_nkw=colchester+drehmaschine".to_string(); | |
let future = fetch(url); | |
let compat_future = future.boxed().unit_error().compat(); | |
tokio::run(compat_future); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment