Recipe scraper implementation (unfinished)
This commit is contained in:
@@ -32,8 +32,10 @@
|
||||
A703226F2ABB1DD700D7C4ED /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */; };
|
||||
A70D7CA12AC73CA800D53DBF /* RecipeEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */; };
|
||||
A70D7CA32AC74B3B00D53DBF /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */; };
|
||||
A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = A74D33BD2AF82AAE00D06555 /* SwiftSoup */; };
|
||||
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */; };
|
||||
A76B8A712AE002AE00096CEC /* AlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76B8A702AE002AE00096CEC /* AlertHandler.swift */; };
|
||||
A781E7612AF822D000452F6F /* RecipeScraper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A781E7602AF822CF00452F6F /* RecipeScraper.swift */; };
|
||||
A7AEAE642AD5521400135378 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A7AEAE632AD5521400135378 /* Localizable.xcstrings */; };
|
||||
A7F3F8E82ACBFC760076C227 /* KeywordPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */; };
|
||||
A7F3F8EA2ACC221C0076C227 /* CategoryPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */; };
|
||||
@@ -86,8 +88,10 @@
|
||||
A703226E2ABB1DD700D7C4ED /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
|
||||
A70D7CA02AC73CA700D53DBF /* RecipeEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeEditView.swift; sourceTree = "<group>"; };
|
||||
A70D7CA22AC74B3B00D53DBF /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
|
||||
A74D33BF2AF82CB500D06555 /* Scraper.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Scraper.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
A76B8A6E2ADFFA8800096CEC /* SupportedLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedLanguage.swift; sourceTree = "<group>"; };
|
||||
A76B8A702AE002AE00096CEC /* AlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertHandler.swift; sourceTree = "<group>"; };
|
||||
A781E7602AF822CF00452F6F /* RecipeScraper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeScraper.swift; sourceTree = "<group>"; };
|
||||
A7AEAE632AD5521400135378 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
A7F3F8E72ACBFC760076C227 /* KeywordPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordPickerView.swift; sourceTree = "<group>"; };
|
||||
A7F3F8E92ACC221C0076C227 /* CategoryPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerView.swift; sourceTree = "<group>"; };
|
||||
@@ -98,6 +102,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A74D33BE2AF82AAE00D06555 /* SwiftSoup in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -149,6 +154,7 @@
|
||||
A70171BA2AB4980100064C43 /* Views */,
|
||||
A70171B72AB2445700064C43 /* ViewModels */,
|
||||
A70171B22AB211F000064C43 /* Network */,
|
||||
A781E75F2AF8228100452F6F /* RecipeImport */,
|
||||
A703226B2ABAF60D00D7C4ED /* Extensions */,
|
||||
A7AEAE632AD5521400135378 /* Localizable.xcstrings */,
|
||||
A70171852AA8E71F00064C43 /* Assets.xcassets */,
|
||||
@@ -246,6 +252,15 @@
|
||||
path = Screenshots;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A781E75F2AF8228100452F6F /* RecipeImport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A781E7602AF822CF00452F6F /* RecipeScraper.swift */,
|
||||
A74D33BF2AF82CB500D06555 /* Scraper.playground */,
|
||||
);
|
||||
path = RecipeImport;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -262,6 +277,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = "Nextcloud Cookbook iOS Client";
|
||||
packageProductDependencies = (
|
||||
A74D33BD2AF82AAE00D06555 /* SwiftSoup */,
|
||||
);
|
||||
productName = "Nextcloud Cookbook iOS Client";
|
||||
productReference = A701717E2AA8E71900064C43 /* Nextcloud Cookbook iOS Client.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -337,6 +355,9 @@
|
||||
fr,
|
||||
);
|
||||
mainGroup = A70171752AA8E71900064C43;
|
||||
packageReferences = (
|
||||
A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
);
|
||||
productRefGroup = A701717F2AA8E71900064C43 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -400,6 +421,7 @@
|
||||
A703226A2ABAF49800D7C4ED /* JSONCoderExtension.swift in Sources */,
|
||||
A703226D2ABAF90D00D7C4ED /* APIController.swift in Sources */,
|
||||
A70171822AA8E71900064C43 /* Nextcloud_Cookbook_iOS_ClientApp.swift in Sources */,
|
||||
A781E7612AF822D000452F6F /* RecipeScraper.swift in Sources */,
|
||||
A70171AD2AA8EF4700064C43 /* MainViewModel.swift in Sources */,
|
||||
A76B8A6F2ADFFA8800096CEC /* SupportedLanguage.swift in Sources */,
|
||||
A70171C92AB4CBB400064C43 /* OnboardingView.swift in Sources */,
|
||||
@@ -579,7 +601,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.3;
|
||||
MARKETING_VERSION = 1.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
@@ -620,7 +642,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.3;
|
||||
MARKETING_VERSION = 1.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "VincentMeilinger.Nextcloud-Cookbook-iOS-Client";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
@@ -761,6 +783,25 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.6.1;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
A74D33BD2AF82AAE00D06555 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = A74D33BC2AF82AAE00D06555 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||
productName = SwiftSoup;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = A70171762AA8E71900064C43 /* Project object */;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swiftsoup",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||
"state" : {
|
||||
"revision" : "8b6cf29eead8841a1fa7822481cb3af4ddaadba6",
|
||||
"version" : "2.6.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -4,10 +4,52 @@
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>MyPlayground (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>Nextcloud Cookbook iOS Client.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>Scraper (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
<key>Scraper (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
<key>Scraper (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// RecipeScraper.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
// Created by Vincent Meilinger on 05.11.23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftSoup
|
||||
|
||||
class RecipeScraper {
|
||||
func scrape(url: String) -> RecipeDetail? {
|
||||
var contents: String? = nil
|
||||
if let url = URL(string: url) {
|
||||
do {
|
||||
contents = try String(contentsOf: url)
|
||||
} catch {
|
||||
print("ERROR: Could not load url content.")
|
||||
}
|
||||
|
||||
} else {
|
||||
print("ERROR: Bad url.")
|
||||
}
|
||||
|
||||
guard let html = contents else {
|
||||
print("ERROR: no contents")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let doc: Document = try SwiftSoup.parse(html)
|
||||
let elements: Elements = try doc.select("script")
|
||||
for elem in elements.array() {
|
||||
for attr in elem.getAttributes()!.asList() {
|
||||
if attr.getValue() == "application/ld+json" {
|
||||
toDict(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func toDict(_ elem: Element) -> [String: Any] {
|
||||
do {
|
||||
let jsonString = try elem.html()
|
||||
//print(json)
|
||||
let json = try JSONSerialization.jsonObject(with: jsonString.data(using: .utf8)!, options: .fragmentsAllowed)
|
||||
if let recipe = json as? [String : Any] {
|
||||
return recipe
|
||||
} else if let recipe = (json as! [Any])[0] as? [String : Any] {
|
||||
return recipe
|
||||
}
|
||||
} catch {
|
||||
print("COULD NOT DECODE")
|
||||
}
|
||||
}
|
||||
|
||||
private func getRecipe(fromDict recipe: Dictionary<String, Any>) {
|
||||
if recipe["@type"] as? String ?? "" == "Recipe" {
|
||||
print(recipe["name"] ?? "No name")
|
||||
print(recipe["recipeIngredient"] ?? "No ingredients")
|
||||
print(recipe["recipeInstruction"] ?? "No instruction")
|
||||
} else if (recipe["@type"] as? [String] ?? []).contains("Recipe") {
|
||||
print(recipe["name"] ?? "No name")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import SwiftSoup
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
//let url = "https://www.chefkoch.de/rezepte/1385981243676608/Knusprige-Entenbrust.html"
|
||||
let url = "https://www.allrecipes.com/recipe/234620/mascarpone-mashed-potatoes/"
|
||||
var contents: String? = nil
|
||||
if let url = URL(string: url) {
|
||||
do {
|
||||
contents = try String(contentsOf: url)
|
||||
//print(contents)
|
||||
} catch {
|
||||
print("ERROR: Could not load url content.")
|
||||
}
|
||||
|
||||
} else {
|
||||
print("ERROR: Bad url.")
|
||||
}
|
||||
|
||||
guard let html = contents else {
|
||||
print("ERROR: no contents")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
let doc: Document = try SwiftSoup.parse(html)
|
||||
let elements: Elements = try doc.select("script")
|
||||
for elem in elements.array() {
|
||||
for attr in elem.getAttributes()!.asList() {
|
||||
//print(attr.getValue())
|
||||
if attr.getValue() == "application/ld+json" {
|
||||
|
||||
do {
|
||||
let jsonString = try elem.html()
|
||||
//print(json)
|
||||
let json = try JSONSerialization.jsonObject(with: jsonString.data(using: .utf8)!, options: .fragmentsAllowed)
|
||||
if let recipe = json as? [String : Any] {
|
||||
print("1")
|
||||
getRecipe(fromDict: recipe)
|
||||
} else if let recipe = (json as! [Any])[0] as? [String : Any] {
|
||||
print("2")
|
||||
getRecipe(fromDict: recipe)
|
||||
}
|
||||
|
||||
|
||||
} catch {
|
||||
print("COULD NOT DECODE")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getRecipe(fromDict recipe: Dictionary<String, Any>) {
|
||||
|
||||
if recipe["@type"] as? String ?? "" == "Recipe" {
|
||||
print(recipe["name"] ?? "No name")
|
||||
print(recipe["recipeIngredient"] ?? "No ingredients")
|
||||
print(recipe["recipeInstruction"] ?? "No instruction")
|
||||
} else if (recipe["@type"] as? [String] ?? []).contains("Recipe") {
|
||||
print(recipe["name"] ?? "No name")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='5.0' target-platform='ios' buildActiveScheme='true' importAppTypes='true'>
|
||||
<timeline fileName='timeline.xctimeline'/>
|
||||
</playground>
|
||||
Reference in New Issue
Block a user