Преглед изворни кода

Scrape images from sources

Tatiana Inama пре 6 година
родитељ
комит
286c135d66

+ 17 - 1
ktchn/src/recipes/controller.ts

@@ -6,6 +6,7 @@ import Scrape from './scrape/index';
 import { ScrapedRecipe } from './model';
 import Ingredient from '../ingredients/model';
 import { dissoc } from 'ramda';
+import request from 'request-promise';
 import fs from 'fs';
 
 function validRecipe(data: any): Promise<Recipe> {
@@ -27,6 +28,19 @@ const getSuggestions = (db: IMongoService) => async function(ingredient: IIngred
   }
 }
 
+async function getImage(src?: string): Promise<string> {
+  if (src) {
+    try {
+      const result = await request({ uri: src, resolveWithFullResponse: true, encoding: null });
+      const data = "data:" + result.headers["content-type"] + ";base64," + new Buffer(result.body).toString('base64');
+      return data;
+    } catch (e) {
+      return ''
+    }
+  }
+  return Promise.resolve('')
+}
+
 const scrapeRecipe: (db: IMongoService) => ChainPController<void, ScrapedRecipe> = (db) => (req) => () => {
   return Scrape(req.body.url)
     .then(async scrapedRecipe => {
@@ -37,9 +51,11 @@ const scrapeRecipe: (db: IMongoService) => ChainPController<void, ScrapedRecipe>
           ingredients: subgroupIngredients
         });
       }));
+      const image = await getImage(scrapedRecipe.image);
       return {
         ...scrapedRecipe,
-        ingredients: recipeIngredients
+        ingredients: recipeIngredients,
+        image,
       }
     })
 }

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

@@ -1,6 +1,8 @@
 import { Recipe, Author, RecipeDetails, ISubRecipe } from '../../model';
 import { parseIngredients } from '../index';
 import { getText, getAttr, getTextList } from './../service';
+import request from 'request-promise';
+import { resolve } from 'dns';
 
 const SELECTORS = {
   TITLE: 'h1#recipe-main-content',
@@ -14,6 +16,7 @@ const SELECTORS = {
   TAGS: 'meta[itemprop="recipeCategory"]',
   DATETIME: 'datetime',
   CONTENT: 'content',
+  IMAGE: 'img.rec-photo',
 }
 
 function getRecipeDetails($: CheerioSelector): RecipeDetails {
@@ -62,6 +65,7 @@ const AR_CONFIG = {
     instructions: getTextList($)(SELECTORS.INSTRUCTIONS),
     tags: $(SELECTORS.TAGS).map((i, e) => $(e).attr('content')).get(),
     summary: getAttr($)(SELECTORS.SUMMARY, SELECTORS.CONTENT),
+    image:  $(SELECTORS.IMAGE).attr('src')
   }),
 }
 

+ 3 - 1
ktchn/src/recipes/scrape/sources/home-cooking-adventure.ts

@@ -10,7 +10,8 @@ const SELECTORS = {
   SERVINGS: 'span.ingheading',
   INGREDIENTS: 'div#ingredients_bg ul li',
   INSTRUCTIONS: 'div#preparation ol li',
-  SUMMARY: 'span[itemprop="summary"]'
+  SUMMARY: 'span[itemprop="summary"]',
+  IMAGE: 'div#main_img img'
 }
 
 function getRecipeName($: CheerioSelector): string {
@@ -66,6 +67,7 @@ const HCA_CONFIG = {
     ingredients: getIngredients($),
     instructions: getInstructions($),
     summary: $(SELECTORS.SUMMARY).children().first().text().trim(),  
+    image: `https://www.homecookingadventure.com${$(SELECTORS.IMAGE).attr('src')}`,
   }),
 }
 

+ 2 - 0
ktchn/src/recipes/scrape/sources/joy-of-baking.ts

@@ -9,6 +9,7 @@ const JOB_AUTHOR_DATA: Author = {
 
 const SELECTORS = {
   TITLE: 'h1 > span.Heading',
+  IMAGE: 'p[align="left"] img'
 }
 
 function getRecipeName($: CheerioSelector): string {
@@ -54,6 +55,7 @@ const JOB_CONFIG = {
       details: new RecipeDetails(), // There is no way to scrape this data from JOB
       ingredients: getIngredients($),
       instructions: getInstructions($),
+      image: `${JOB_AUTHOR_DATA.website}/${$(SELECTORS.IMAGE).attr('src')}`
     }; 
   }
 }

+ 3 - 1
ktchn/src/recipes/scrape/sources/just-one-cookbook.ts

@@ -21,7 +21,8 @@ const SELECTORS = {
   INSTRUCTIONS: 'div.wprm-recipe-instruction-text',
   TAGS: 'span.wprm-recipe-keyword',
   SUMMARY: 'div.wprm-recipe-summary',
-  COURSE: 'span.wprm-recipe-course'
+  COURSE: 'span.wprm-recipe-course',
+  IMAGE: 'div.wprm-recipe-image img'
 }
 
 const getRecipeName = ($: CheerioSelector): string => {
@@ -76,6 +77,7 @@ const JOC_CONFIG = {
     tags: (getText($)(SELECTORS.TAGS)).split(',').map(R.trim),
     course: [getText($)(SELECTORS.COURSE)],
     summary: getText($)(SELECTORS.SUMMARY),
+    image: $(SELECTORS.IMAGE).attr('nitro-lazy-src')
   })
 }
 

+ 10 - 0
ktchn/src/recipes/scrape/sources/laura-in-the-kitchen.ts

@@ -12,6 +12,7 @@ const SELECTORS = {
   DETAILS: 'script[type="application/ld+json"]',
   INGREDIENTS: '.cs-ingredients-check-list > ul',
   INSTRUCTIONS: '#recipe-process > ul',
+  IMAGE: 'div.ytp-cued-thumbnail-overlay-image'
 }
 
 function getRecipeDetails($: CheerioSelector): RecipeDetails {
@@ -48,6 +49,14 @@ function getInstructions($: CheerioSelector): string[] {
   return data.split('\n').filter(s => s !== '').map(s => s.trim().replace(/^\d\)\s*/, ''));
 }
 
+const getImage = ($: CheerioSelector) => {
+  try {
+    const image = `${LITK_AUTHOR_DATA.website}/500x300thumbnails/${getText($)(SELECTORS.TITLE).toLowerCase().replace(/ /g, '-')}.jpg`;
+    return image
+  } catch (e) {
+    return ''
+  }
+}
 const LITK_CONFIG = {
   name: 'Laura in the Kitchen',
   domain: 'laurainthekitchen',
@@ -58,6 +67,7 @@ const LITK_CONFIG = {
     details: getRecipeDetails($),
     ingredients: getIngredients($),
     instructions: getInstructions($),
+    image: getImage($)
   })
 }
 

+ 1 - 1
recipes/src/components/ImageUploader/index.tsx

@@ -18,7 +18,7 @@ export const ImageUploader: React.FunctionComponent<ImageUploaderProps> = ({ ini
     image: ''
   });
 
-  const isBase64 = (file: string): boolean => !file.indexOf('base64');
+  const isBase64 = (file: string): boolean => !!file.indexOf('base64');
 
   const fileInput: React.RefObject<HTMLInputElement> = useRef(null);