Kaynağa Gözat

Scraping service for All Recipes. TG-4 #in-progress

Tati 7 yıl önce
ebeveyn
işleme
d3fe6ec012

+ 5 - 5
ktchn/src/recipes/index.ts

@@ -23,16 +23,16 @@ interface Temperature {
 }
 
 export class RecipeDetails {
-  [key:string]: number 
-  preparationTime: number;
-  cookingTime: number;
-  servings: number;
+  [key:string]: number|string 
+  preparationTime: number|string; // TODO: Change this type to string when all scrapes get ISO8601 duration format
+  cookingTime: number|string;
+  servings: number|string;
 
   constructor(
     prepTime:number|string = 0,
     cookingTime:number|string = 0,
     servings:number|string = 0) {
-      this.preparationTime = Number( );
+      this.preparationTime = Number(prepTime);
       this.cookingTime = Number(cookingTime);
       this.servings = Number(servings);
     }

+ 73 - 0
ktchn/src/recipes/scrape/sources/all-recipes.ts

@@ -0,0 +1,73 @@
+import { Recipe, Author, RecipeDetails, ComposedIngredients } from '../../index';
+import { parseIngredients } from '../index';
+
+const SELECTORS = {
+  PREP_TIME: 'time[itemprop="prepTime"]',
+  COOK_TIME: 'time[itemprop="cookTime"]',
+  SERVINGS: 'meta#metaRecipeServings',
+  INGREDIENTS: 'div#polaris-app',
+  INSTRUCTIONS: 'li.step',
+}
+
+function getRecipeName($: CheerioSelector): string {
+  return $('h1#recipe-main-content').text();
+}
+
+function getAuthorData($: CheerioSelector): Author {
+  return {
+    name: $('span.submitter__name').text(),
+  };
+}
+
+function getRecipeDetails($: CheerioSelector): RecipeDetails {
+  return {
+    preparationTime: $(SELECTORS.PREP_TIME).attr('datetime'),
+    cookingTime: $(SELECTORS.COOK_TIME).attr('datetime'),
+    servings: Number($(SELECTORS.SERVINGS).attr('content')),
+  };
+}
+
+function getIngredients($: CheerioSelector): ComposedIngredients[] {
+  const rawData = $(SELECTORS.INGREDIENTS).find('li.checkList__line');
+  const isSubtitle = (element: Cheerio) => element.find('input').data('id') === 0;
+  let ingredients: ComposedIngredients[] = [{
+    name: '',
+    ingredients: [],
+  }];
+  rawData.each((i, element)=> {
+    const label = $(element).find('label');
+    if (label.attr('title')) { // matches items with ingredients
+      if (isSubtitle(label)) {
+        ingredients.push({
+          name: label.text().trim(),
+          ingredients: [],
+        });
+      } else {
+        const last = ingredients.length - 1;
+        ingredients[last].ingredients = ingredients[last].ingredients.concat([parseIngredients(label.text().trim())])
+      }
+    }
+  })
+  return ingredients;
+}
+
+function getInstructions($: CheerioSelector): string[] {
+  const rawData = $(SELECTORS.INSTRUCTIONS).toArray().map(element => $(element).text().trim());
+  console.log(JSON.stringify(rawData));
+  return rawData;
+}
+
+const AR_CONFIG = {
+  name: 'All Recipes',
+  domain: 'allrecipes',
+  website: 'https://www.allrecipes.com',
+  scrapeRecipe: ($: CheerioSelector): Recipe => new Recipe(
+    getRecipeName($),
+    getAuthorData($),
+    getRecipeDetails($),
+    getIngredients($),
+    getInstructions($),
+  ),
+}
+
+export default AR_CONFIG;

+ 3 - 0
ktchn/src/recipes/scrape/sources/index.ts

@@ -1,7 +1,9 @@
 import LITK from "./laura-in-the-kitchen";
 import JOB from './joy-of-baking';
+import AR from './all-recipes';
 import { Recipe } from '../../index';
 
+
 export interface ScrapingSource {
   name: string,
   domain: string,
@@ -12,6 +14,7 @@ export interface ScrapingSource {
 const Sources: ScrapingSource[] = [
   LITK,
   JOB,
+  AR,
 ];
 
 export default Sources;