Skip to content

Instantly share code, notes, and snippets.

@drogus
Last active December 28, 2018 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drogus/0b11aa2b26c7dc0bd7fa833b45649ca2 to your computer and use it in GitHub Desktop.
Save drogus/0b11aa2b26c7dc0bd7fa833b45649ca2 to your computer and use it in GitHub Desktop.
[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"
#![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