laura-in-the-kitchen.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { Recipe, RecipeDetails, Ingredient, ComposedIngredients, Author } from '../../index';
  2. import { parse as parseIngredient } from "recipe-ingredient-parser";
  3. const LITK_AUTHOR_DATA: Author = {
  4. name: 'Laura Vitale',
  5. website: 'http://www.laurainthekitchen.com',
  6. };
  7. const SELECTORS = {
  8. TITLE: '.cs-page-title > h1',
  9. DETAILS: '.cs-recipe-details > div',
  10. INGREDIENTS: '.cs-ingredients-check-list > ul',
  11. INSTRUCTIONS: '#recipe-process > ul',
  12. }
  13. function getRecipeName($: CheerioSelector): string {
  14. return $(SELECTORS.TITLE).text().trim();
  15. }
  16. function getRecipeDetails($: CheerioSelector): RecipeDetails {
  17. const matchInt = (data: string, reg: RegExp, logic?: (s: RegExpMatchArray) => number): number => {
  18. let result = data.match(reg);
  19. return logic ?
  20. (result ? logic(result) : 0) :
  21. (result ? parseInt(result[0]) : 0)
  22. };
  23. const parseTime = (data: string) => {
  24. let parsedHoursAsMin = matchInt(data, /(\d+) hours?/, result => parseInt(result[0])*60);
  25. let parsedMinutes = matchInt(data, /(\d+) minutes?/);
  26. return parsedHoursAsMin + parsedMinutes;
  27. };
  28. const LAURA_MAP: {
  29. [index:string] : {
  30. key: string,
  31. transform: (data: string) => number,
  32. }
  33. } = {
  34. preparationTime: {
  35. key: 'Preparation',
  36. transform: parseTime,
  37. },
  38. cookingTime: {
  39. key: 'Cook time',
  40. transform: parseTime,
  41. },
  42. servings: {
  43. key: 'Servings',
  44. transform: (data: string) => matchInt(data, /\d+/),
  45. }
  46. };
  47. let details = new RecipeDetails();
  48. const detailsKey = Object.keys(details);
  49. const data = $(SELECTORS.DETAILS);
  50. let _rawData: any = {};
  51. data.each((i, el) => {
  52. let key: string = $(el).find('span').text();
  53. _rawData[key] = $(el).contents().last().text();
  54. });
  55. return detailsKey.reduce((recipeDetails, key) => {
  56. let _key = LAURA_MAP[key];
  57. return {
  58. ...recipeDetails,
  59. [key]: _key.transform(_rawData[_key.key])
  60. }
  61. }, details);
  62. }
  63. function getIngredients($: CheerioSelector): ComposedIngredients[] {
  64. const parseIngredients = (rawIngredient: string): Ingredient => {
  65. let parsed = parseIngredient(rawIngredient);
  66. return {
  67. name: parsed.ingredient,
  68. quantity: Number(parsed.quantity) || 0,
  69. unit: parsed.unit || '',
  70. _original: rawIngredient,
  71. };
  72. };
  73. const ingredientsScrape = $(SELECTORS.INGREDIENTS).children();
  74. const isNewIngredientList = (tagName: string): boolean => tagName === 'span';
  75. let ingredients: ComposedIngredients[] = [];
  76. return ingredientsScrape.toArray().reduce((list, rawIngredient)=>{
  77. let last = list.length - 1;
  78. let text = $(rawIngredient).text();
  79. if (isNewIngredientList(rawIngredient.tagName)) {
  80. return list.concat([{name: text, ingredients: []}]);
  81. } else {
  82. list[last].ingredients = list[last].ingredients.concat([parseIngredients(text)]);
  83. return list;
  84. }
  85. }, ingredients);
  86. }
  87. function getInstructions($: CheerioSelector): string[] {
  88. const data = $(SELECTORS.INSTRUCTIONS).text();
  89. return data.split('\n').filter(s => s !== '').map(s => s.trim().replace(/^\d\)\s*/, ''));
  90. }
  91. const LITK_CONFIG = {
  92. name: 'Laura in the Kitchen',
  93. domain: 'laurainthekitchen',
  94. website: LITK_AUTHOR_DATA.website,
  95. scrapeRecipe: ($: CheerioSelector): Recipe => ({
  96. name: getRecipeName($),
  97. author: LITK_AUTHOR_DATA,
  98. details: getRecipeDetails($),
  99. ingredients: getIngredients($),
  100. instructions: getInstructions($),
  101. })
  102. }
  103. export default LITK_CONFIG;